2012-06-28 4 views
6

Quando si lavora con JSP o con altri linguaggi convertiti in codice sorgente Java (o in stub), spesso viene generato un file SMAP che può essere successivamente incorporato nel file di classe per i debugger per mostrare migliori tracce dello stack (o nel caso di Jasper viene incorporato automaticamente).Come aggiungere le informazioni SMAP JSR-045 agli stacktraces Java in fase di runtime?

C'è un old JVM bug (or RFE) per aggiungere il supporto per includere le informazioni SMAP nelle tracce dello stack, ma dalla mancanza di attività sembra che i ragazzi Sun/Oracle preferiscano che tutti post-elaborino le tracce dello stack stesso.

Quindi, ecco la mia domanda: come fare questo? Ci sono biblioteche in giro che fanno il duro lavoro per te, o devi implementare tutto da solo?

Ho già trovato un buon posto in cui ho accesso sia all'oggetto eccezione che al programma di caricamento classi che ha caricato le classi "SMAP abilitate". Ora avrei dovuto

  • iterate sopra la traccia dello stack
  • Verificare ogni voce se riesco a trovare la classe
  • Analizzare la classe con e. g. ASM per estrarre informazioni SMAP
  • Scrivi un parser SMAP che analizza la mappatura linea di retromarcia ei nomi dei file su informazioni SMAP
  • Sostituire l'elemento traccia dello stack con uno nuovo basato sulla mappatura (o in alternativa aggiungere un nuovo uno? Cosa è meglio?)
  • Memorizza alcune informazioni in modo che non debba ripetere la stessa operazione se la traccia dello stack identica (o simile) ricompaia pochi secondi dopo.

E dal momento che sembra essere un compito noioso e soggetto a errori, spero che qualcuno già ha fatto questo e non mi resta che aggiungere una libreria mie dipendenze e chiamare un metodo makeStacktraceFancy per le mie eccezioni per rendere la pilatrace fantasia prima di registrarli.

risposta

3

Come nessuno sembra sapere di una soluzione esistente, ho arrotolato il mio veloce & sporco.

Non supporta tutte le funzionalità SMAP (analizza solo il primo strato e ignora le sezioni del fornitore e le informazioni statum predefinite), ma è sufficiente per le mie esigenze.

Poiché il codice per estrarre l'attributo SMAP dalla classe è solo di circa 50 righe, ho deciso di reimplementarlo anziché aggiungere ASM come dipendenza. Il codice per come usarlo con ASM è nei commenti.

Come è stato testato solo pochissimo (in alcuni casi di test), modificherò il post in caso di errori gravi.

codice è qui sotto:

/* 
* 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; 
     } 
    } 
} 
+0

Tutti i diritti riservati? Su una risposta di overflow dello stack? Era questo di proposito? –

+0

@RyanTheLeach: questo è accaduto nel 2012, quando il codice di overflow dello stack più lungo di 10 righe senza un'intestazione di licenza Open Source esplicita era automaticamente concesso in licenza CC-BY-SA, che in effetti (almeno per tutti in Europa) significava che non potevano legalmente includere in un software closed source senza prima chiedere agli autori. – mihi

+1

@RyanTheLeach Per la cronaca, sto perfettamente bene con la politica corrente (a partire da febbraio 2016) per i nuovi post, che se si aggiunge un link al post StackOverflow è possibile utilizzare qualsiasi codice unter la licenza permissiva del MIT. Questo include il mio codice sopra (quindi considera la licenza BSD con doppia licenza e la licenza StackOverflow MIT). Se hai bisogno di qualcos'altro, probabilmente potrei concederlo anche a te. – mihi

1

Non sai cosa stai cercando di ottenere qui. Se hai solo bisogno di mostrare jsp e il numero di riga nella traccia stack quando è loggato, allora il più semplice sarebbe sostituire il logger e mostrare il numero di linea jsp da smap nella traccia dello stack stampato. Ecco un patch for log4j che esegue modifiche un po 'simili alla traccia dello stack.

Anche se, non sarà possibile ottenere la navigazione automatica da un nome di classe a JSP in IDE in corso ...

PS: A proposito, se si implementa SMAP parser, sarebbe una grande idea di contribuire esso torna al progetto ASM ...

+0

ottenendo informazioni nel registro non è il problema. È possibile ottenere getStackTrace(), quindi modificare nuovamente gli elementi dell'array e setStackTrace(). In tal modo si evita anche il frazionamento delle stringhe, poiché 'StackTraceElement' ha parti separate per classe, metodo, file, lino. L'analisi dello SMAP è decisamente più difficile (poiché richiede un po 'di tempo per l'implementazione corretta). – mihi

Problemi correlati