草庐IT

java - 如何在运行时将 JSR-045 SMAP 信息添加到 Java 堆栈跟踪?

coder 2024-04-01 原文

当使用 JSP 或其他转换为 Java 源代码(或 stub )的语言时,通常会生成一个 SMAP 文件,稍后可以将其嵌入到类文件中以供调试器显示更好的堆栈跟踪(或在对于 Jasper,它会自动嵌入)。

有一个old JVM bug (or RFE)添加支持以在堆栈跟踪中包含 SMAP 信息,但由于缺乏 Activity ,Sun/Oracle 人员似乎更喜欢每个人自己对堆栈跟踪进行后处理。

所以这是我的问题:如何做到这一点?周围是否有图书馆为您完成繁重的工作,还是您必须自己实现一切?

我已经找到了一个可以访问异常对象和加载“启用 SMAP”类的类加载器的好地方。现在我必须

  • 遍历堆栈跟踪
  • 如果我能找到类,请检查每个条目
  • 用 e 分析类(class)。 G。 ASM提取 SMAP 信息
  • 编写一个 SMAP 解析器,从 SMAP 信息中解析反向行映射和文件名
  • 根据映射用一个新元素替换堆栈跟踪元素(或者添加一个新元素?哪个更好?)
  • 缓存一些信息,以便在几秒钟后再次出现完全相同(或相似)的堆栈跟踪时,我不必再次执行相同的操作。

因为这似乎是一项乏味且容易出错的任务,我希望有人已经这样做了,我只需要向我的依赖项添加一个库并调用一个 makeStacktraceFancy 方法我的异常(exception)是在我记录它们之前让堆栈跟踪看起来很漂亮。

最佳答案

由于似乎没有人知道现有的解决方案,所以我推出了自己的快速而肮脏的解决方案。

它不支持所有 SMAP 功能(它只解析第一层,并忽略供应商部分和默认状态信息),但足以满足我的需求。

由于从类中提取 SMAP 属性的代码只有大约 50 行,我决定重新实现它而不是将 ASM 添加为依赖项。有关如何将其与 ASM 一起使用的代码在评论中。

因为它只进行了很少的测试(在一些测试用例上),如果我遇到任何严重的错误,我会编辑帖子。

代码如下:

/* 
 * SMAPSourceDebugExtension.java - Parse source debug extensions and
 * enhance stack traces.
 * 
 * Copyright (c) 2012 Michael Schierl
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *   
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *   
 * - Neither name of the copyright holders nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *   
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND THE CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDERS OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package smap;

import java.io.*;
import java.util.*;
import java.util.regex.*;

/**
 * Utility class to parse Source Debug Extensions and enhance stack traces.
 * 
 * Note that only the first stratum is parsed and used.
 * 
 * @author Michael Schierl
 */
public class SMAPSourceDebugExtension {

    /**
     * Enhance a stack trace with information from source debug extensions.
     * 
     * @param t
     *            Throwable whose stack trace should be enhanced
     * @param cl
     *            Class loader to load source debug extensions from
     * @param keepOriginalFrames
     *            Whether to keep the original frames referring to Java source
     *            or drop them
     * @param packageNames
     *            Names of packages that should be scanned for source debug
     *            extensions, or empty to scan all packages
     * @throws IOException
     *             if an I/O error occurs
     */
    public static void enhanceStackTrace(Throwable t, ClassLoader cl, boolean keepOriginalFrames, String... packageNames) throws IOException {
        enhanceStackTrace(t, new HashMap<String, SMAPSourceDebugExtension>(), cl, keepOriginalFrames, packageNames);
    }

    /**
     * Enhance a stack trace with information from source debug extensions.
     * Provide a custom cache of already resolved and parsed source debug
     * extensions, to avoid parsing them for every new exception.
     * 
     * @param t
     *            Throwable whose stack trace should be enhanced
     * @param cache
     *            Cache to be used and filled
     * @param cl
     *            Class loader to load source debug extensions from
     * @param keepOriginalFrames
     *            Whether to keep the original frames referring to Java source
     *            or drop them
     * @param packageNames
     *            Names of packages that should be scanned for source debug
     *            extensions, or empty to scan all packages
     * @throws IOException
     *             if an I/O error occurs
     */
    public static void enhanceStackTrace(Throwable t, Map<String, SMAPSourceDebugExtension> cache, ClassLoader cl, boolean keepOriginalFrames, String... packageNames) throws IOException {
        StackTraceElement[] elements = t.getStackTrace();
        List<StackTraceElement> newElements = null;
        for (int i = 0; i < elements.length; i++) {
            String className = elements[i].getClassName();
            SMAPSourceDebugExtension smap = cache.get(className);
            if (smap == null) {
                boolean found = false;
                for (String packageName : packageNames) {
                    if (className.startsWith(packageName + ".")) {
                        found = true;
                        break;
                    }
                }
                if (found || packageNames.length == 0) {
                    InputStream in = cl.getResourceAsStream(className.replace('.', '/') + ".class");
                    if (in != null) {
                        String value = extractSourceDebugExtension(in);
                        in.close();
                        if (value != null) {
                            value = value.replaceAll("\r\n?", "\n");
                            if (value.startsWith("SMAP\n")) {
                                smap = new SMAPSourceDebugExtension(value);
                                cache.put(className, smap);
                            }
                        }
                    }
                }
            }
            StackTraceElement newFrame = null;
            if (smap != null) {
                int[] inputLineInfo = smap.reverseLineMapping.get(elements[i].getLineNumber());
                if (inputLineInfo != null && elements[i].getFileName().equals(smap.generatedFileName)) {
                    FileInfo inputFileInfo = smap.fileinfo.get(inputLineInfo[0]);
                    if (inputFileInfo != null) {
                        newFrame = new StackTraceElement("[" + smap.firstStratum + "]", inputFileInfo.path, inputFileInfo.name, inputLineInfo[1]);
                    }
                }
            }
            if (newFrame != null) {
                if (newElements == null) {
                    newElements = new ArrayList<StackTraceElement>(Arrays.asList(elements).subList(0, i));
                }
                if (keepOriginalFrames)
                    newElements.add(elements[i]);
                newElements.add(newFrame);
            } else if (newElements != null) {
                newElements.add(elements[i]);
            }
        }
        if (newElements != null) {
            t.setStackTrace(newElements.toArray(new StackTraceElement[newElements.size()]));
        }
        if (t.getCause() != null)
            enhanceStackTrace(t.getCause(), cache, cl, keepOriginalFrames, packageNames);
    }

    /**
     * Extract source debug extension from a class file, provided as an input
     * stream
     * 
     * @param in
     *            Input stream to read the class file
     * @return Source debug extension as a String, or <code>null</code> if none
     *         was found.
     * @throws IOException
     *             if an I/O error occurs
     */
//    // ASM version of the same method:
//    private static String extractSourceDebugExtension0(InputStream in) throws IOException {
//        ClassReader cr = new ClassReader(in);
//        final String[] result = new String[1];
//        cr.accept(new ClassVisitor(Opcodes.ASM4) {
//            @Override
//            public void visitSource(String source, String debug) {
//                result[0] = debug;
//            }
//        }, 0);
//        return result[0];
//    }
    private static String extractSourceDebugExtension(InputStream in) throws IOException {
        DataInputStream dis = new DataInputStream(in);
        boolean[] isSourceDebugExtension;
        dis.skipBytes(8);

        // read constant pool
        isSourceDebugExtension = new boolean[dis.readUnsignedShort()];
        int[] skipSizes = new int[] { 0, 0, 2, 4, 4, 0, 0, 2, 2, 4, 4, 4, 4, 2, 2, 3, 2, 2, 4 };
        for (int i = 1; i < isSourceDebugExtension.length; i++) {
            byte type = dis.readByte();
            int skipSize;
            if (type == 1) {
                String value = dis.readUTF();
                isSourceDebugExtension[i] = value.equals("SourceDebugExtension");
                skipSize = 0;
            } else if (type == 5 || type == 6) {
                skipSize = 8;
                i++;
            } else if (type > 1 && type < 19) {
                skipSize = skipSizes[type];
            } else {
                skipSize = 2;
            }
            dis.skipBytes(skipSize);
        }
        dis.skipBytes(6);
        int ifaces = dis.readUnsignedShort();
        dis.skipBytes(2 * ifaces);

        // skip fields and methods
        for (int k = 0; k < 2; k++) {
            int count = dis.readUnsignedShort();
            for (int i = 0; i < count; i++) {
                dis.skipBytes(6);
                int attrCount = dis.readUnsignedShort();
                for (int j = 0; j < attrCount; j++) {
                    dis.skipBytes(2);
                    int skip = dis.readInt();
                    dis.skipBytes(skip);
                }
            }
        }

        // read attributes and find SourceDebugExtension
        int attrCount = dis.readUnsignedShort();
        for (int i = 0; i < attrCount; i++) {
            int idx = dis.readUnsignedShort();
            int len = dis.readInt();
            if (isSourceDebugExtension[idx]) {
                byte[] buf = new byte[len];
                dis.readFully(buf);
                return new String(buf, "UTF-8");
            } else {
                dis.skipBytes(len);
            }
        }
        return null;
    }

    private final String generatedFileName, firstStratum;
    private final Map<Integer, FileInfo> fileinfo = new HashMap<Integer, FileInfo>();
    private final Map<Integer, int[]> reverseLineMapping = new HashMap<Integer, int[]>();

    private static final Pattern LINE_INFO_PATTERN = Pattern.compile("([0-9]+)(?:#([0-9]+))?(?:,([0-9]+))?:([0-9]+)(?:,([0-9]+))?");

    private SMAPSourceDebugExtension(String value) {
        String[] lines = value.split("\n");
        if (!lines[0].equals("SMAP") || !lines[3].startsWith("*S ") || !lines[4].equals("*F"))
            throw new IllegalArgumentException(value);
        generatedFileName = lines[1];
        firstStratum = lines[3].substring(3);
        int idx = 5;
        while (!lines[idx].startsWith("*")) {
            String infoline = lines[idx++], path = null;
            if (infoline.startsWith("+ ")) {
                path = lines[idx++];
                infoline = infoline.substring(2);
            }
            int pos = infoline.indexOf(" ");
            int filenum = Integer.parseInt(infoline.substring(0, pos));
            String name = infoline.substring(pos + 1);
            fileinfo.put(filenum, new FileInfo(name, path == null ? name : path));
        }
        if (lines[idx].equals("*L")) {
            idx++;
            int lastLFI = 0;
            while (!lines[idx].startsWith("*")) {
                Matcher m = LINE_INFO_PATTERN.matcher(lines[idx++]);
                if (!m.matches())
                    throw new IllegalArgumentException(lines[idx - 1]);
                int inputStartLine = Integer.parseInt(m.group(1));
                int lineFileID = m.group(2) == null ? lastLFI : Integer.parseInt(m.group(2));
                int repeatCount = m.group(3) == null ? 1 : Integer.parseInt(m.group(3));
                int outputStartLine = Integer.parseInt(m.group(4));
                int outputLineIncrement = m.group(5) == null ? 1 : Integer.parseInt(m.group(5));
                for (int i = 0; i < repeatCount; i++) {
                    int[] inputMapping = new int[] { lineFileID, inputStartLine + i };
                    int baseOL = outputStartLine + i * outputLineIncrement;
                    for (int ol = baseOL; ol < baseOL + outputLineIncrement; ol++) {
                        if (!reverseLineMapping.containsKey(ol))
                            reverseLineMapping.put(ol, inputMapping);
                    }
                }
                lastLFI = lineFileID;
            }
        }
    }

    private static class FileInfo {
        public final String name, path;

        public FileInfo(String name, String path) {
            this.name = name;
            this.path = path;
        }
    }
}

关于java - 如何在运行时将 JSR-045 SMAP 信息添加到 Java 堆栈跟踪?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11250834/

有关java - 如何在运行时将 JSR-045 SMAP 信息添加到 Java 堆栈跟踪?的更多相关文章

  1. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  2. ruby - 我需要将 Bundler 本身添加到 Gemfile 中吗? - 2

    当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/

  3. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  4. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  5. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  6. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  7. ruby - 将 Bootstrap Less 添加到 Sinatra - 2

    我有一个ModularSinatra应用程序,我正在尝试将Bootstrap添加到应用程序中。get'/bootstrap/application.css'doless:"bootstrap/bootstrap"end我在views/bootstrap中有所有less文件,包括bootstrap.less。我收到这个错误:Less::ParseErrorat/bootstrap/application.css'reset.less'wasn'tfound.Bootstrap.less的第一行是://CSSReset@import"reset.less";我尝试了所有不同的路径格式,但它

  8. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  9. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  10. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

随机推荐