如何在运行时将JSR-045 SMAP信息添加到Java stacktraces?

如何在运行时将JSR-045 SMAP信息添加到Java stacktraces?,java,jsp,bytecode,Java,Jsp,Bytecode,当使用JSP或转换为Java源代码(或存根)的其他语言时,通常会生成一个SMAP文件,该文件可以稍后嵌入到类文件中,以便调试器显示更好的堆栈跟踪(或者在Jasper的情况下,它会自动嵌入) 在堆栈跟踪中包含SMAP信息需要增加支持,但由于缺少活动,Sun/Oracle的人员似乎更希望每个人都自己对堆栈跟踪进行后期处理 所以我的问题是:如何做到这一点?周围是否有图书馆为您做这些艰苦的工作,或者您必须自己实现所有事情 我已经找到了一个可以访问exception对象和加载“支持SMAP”类的类加载器的

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

在堆栈跟踪中包含SMAP信息需要增加支持,但由于缺少活动,Sun/Oracle的人员似乎更希望每个人都自己对堆栈跟踪进行后期处理

所以我的问题是:如何做到这一点?周围是否有图书馆为您做这些艰苦的工作,或者您必须自己实现所有事情

我已经找到了一个可以访问exception对象和加载“支持SMAP”类的类加载器的好地方。现在我不得不

  • 迭代堆栈跟踪
  • 如果我能找到课程,请检查每个条目
  • 用e。G提取SMAP信息的步骤
  • 编写一个SMAP解析器,解析SMAP信息中的反向行映射和文件名
  • 根据映射使用新的堆栈跟踪元素替换堆栈跟踪元素(或者添加一个新元素?什么更好?)
  • 缓存一些信息,这样,如果几秒钟后完全相同(或类似)的堆栈跟踪重新出现,我就不必再做同样的事情

由于这似乎是一项乏味且容易出错的任务,我希望有人已经完成了这项工作,我只需要在依赖项中添加一个库,并为我的异常调用一个
makeStackTraceFuncy
方法,以便在我记录它们之前使stacktraces更有趣。

不确定您在这里想要实现什么。如果在记录堆栈跟踪时只需要在堆栈跟踪中显示jsp和行号,那么最简单的方法是替换logger并在打印的堆栈跟踪中显示来自smap的jsp行号。下面是一个对堆栈跟踪进行类似更改的示例

尽管如此,在当前的IDE中,您不会自动从类名导航到jsp


PS:顺便说一句,如果您要实现SMAP解析器,将其贡献回ASM项目将是一个好主意…

因为似乎没有人知道现有的解决方案,我推出了自己的快速脏解决方案

它不支持所有的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;
        }
    }
}

将信息输入日志不是问题。您可以获取stacktrace(),然后只需再次修改数组元素和setStackTrace()。这样也可以避免一些字符串拆分,因为类、方法、文件和行号都有单独的部分。解析SMAP肯定更难(因为在中需要一些时间才能正确实现)。保留所有权利?关于堆栈溢出的回答?这是故意的吗?@RyanTheLeach:这是在2012年,当堆栈溢出代码超过10行且没有明确的开源许可证标头时,自动获得CC-BY-SA许可证,这有效地(至少对欧洲所有人来说)了这意味着他们不能在没有事先询问作者的情况下合法地将其包含到封闭源代码软件中。@RyanTheLeach记录在案,我完全同意当前(截至2016年2月)的新帖子政策,即如果您添加回StackOverflow帖子的链接,您可以在许可的麻省理工许可证下使用任何代码。这包括上面的代码(因此考虑双许可的BSD许可证和StAccExcel MIT许可证)。如果你还需要什么,我可能也会给你。这是有道理的,谢谢,谢谢你的答复。