6

JSPまたはJavaソースコード(またはスタブ)に変換される他の言語で作業する場合、多くの場合、SMAPファイルが生成されます。このファイルは、後でデバッガーのクラスファイルに埋め込んで、より良いスタックトレースを表示できます(またはJasperの場合)。自動的に埋め込まれます)。

スタックトレースにSMAP情報を含めるためのサポートを追加する古いJVMバグ(またはRFE)がありますが、アクティビティがないため、Sun / Oracleの担当者は、誰もが自分のスタックトレースを後処理することを望んでいるようです。

だからここに私の質問があります:これを行う方法は?あなたのために大変な仕事をしているライブラリが周りにありますか、それともすべてを自分で実装する必要がありますか?

「SMAP対応」クラスをロードした例外オブジェクトとクラスローダーの両方にアクセスできる良い場所をすでに見つけました。今私はしなければならないだろう

  • スタックトレースを反復処理します
  • クラスが見つかったら、各エントリを確認してください
  • ASMなどでクラスを分析してSMAP情報を抽出します
  • SMAP情報からリバースラインマッピングとファイル名を解析するSMAPパーサーを作成します
  • マッピングに基づいてスタックトレース要素を新しいものに置き換えます(または、新しい要素を追加しますか?何が良いですか?)
  • まったく同じ(または類似の)スタックトレースが数秒後に再表示された場合に同じことを再度実行する必要がないように、情報の一部をキャッシュします。

そして、それは退屈でエラーが発生しやすいタスクのように思われるので、誰かがすでにこれを行っていることを望んでいます。依存関係にライブラリを追加しmakeStacktraceFancy、例外のメソッドを呼び出して、スタックトレースをログに記録する前にファンシーにする必要があります。 。

4

2 に答える 2

3

誰も既存の解決策を知らないように思われるので、私は自分の手っ取り早い解決策を転がしました。

すべての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;
        }
    }
}
于 2012-07-02T19:12:04.663 に答える
1

ここで何を達成しようとしているのかわからない。ログに記録されたときにスタックトレースにjspと行番号を表示する必要がある場合は、ロガーを置き換えて、印刷されたスタックトレースにsmapからjsp行番号を表示するのが最も簡単です。これは、スタックトレースにいくらか同様の変更を加えるlog4jのパッチです。

ただし、現在のIDEではクラス名からjspへの自動ナビゲーションは取得されません...

PS:ところで、SMAPパーサーを実装する場合は、ASMプロジェクトに貢献することをお勧めします...

于 2012-06-28T19:01:53.980 に答える