/* * Lilith - a log event viewer. * Copyright (C) 2007-2015 Joern Huxhorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright 2007-2013 Joern Huxhorn * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.huxhorn.lilith.data.logging.logback; import ch.qos.logback.classic.spi.ClassPackagingData; import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.classic.spi.LoggerContextVO; import ch.qos.logback.classic.spi.StackTraceElementProxy; import ch.qos.logback.classic.spi.ThrowableProxy; import de.huxhorn.lilith.data.converter.Converter; import de.huxhorn.lilith.data.eventsource.LoggerContext; import de.huxhorn.lilith.data.logging.ExtendedStackTraceElement; import de.huxhorn.lilith.data.logging.LoggingEvent; import de.huxhorn.lilith.data.logging.Marker; import de.huxhorn.lilith.data.logging.Message; import de.huxhorn.lilith.data.logging.MessageFormatter; import de.huxhorn.lilith.data.logging.ThreadInfo; import de.huxhorn.lilith.data.logging.ThrowableInfo; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class LogbackLoggingConverter implements Converter<LoggingEvent> { private static final Method GET_SUPPRESSED_METHOD; static { Method m = null; try { m = IThrowableProxy.class.getMethod("getSuppressed"); } catch(NoSuchMethodException e) { // ignore } GET_SUPPRESSED_METHOD = m; } private static IThrowableProxy[] getSuppressed(IThrowableProxy proxy) { if(GET_SUPPRESSED_METHOD == null || proxy == null) { return null; } try { Object result = GET_SUPPRESSED_METHOD.invoke(proxy); if(result instanceof IThrowableProxy[]) { return (IThrowableProxy[]) result; } } catch(IllegalAccessException | InvocationTargetException e) { // ignore } return null; } private ExtendedStackTraceElement[] convert(StackTraceElement[] stackTrace) { if(stackTrace == null) { return null; } ExtendedStackTraceElement[] result = new ExtendedStackTraceElement[stackTrace.length]; for(int i = 0; i < stackTrace.length; i++) { result[i] = new ExtendedStackTraceElement(stackTrace[i]); } return result; } ThrowableInfo initFromThrowableProxy(IThrowableProxy ti, boolean calculatePackagingData) { if(ti == null) { return null; } /* CHECK: java.lang.IllegalStateException: Packaging data has been already set if(calculatePackagingData && ti instanceof ThrowableProxy) { ThrowableProxy tp= (ThrowableProxy) ti; tp.calculatePackagingData(); } */ ThrowableInfo result = new ThrowableInfo(); result.setName(ti.getClassName()); result.setOmittedElements(ti.getCommonFrames()); result.setMessage(ti.getMessage()); result.setStackTrace(initFromStackTraceElementProxyArray(ti.getStackTraceElementProxyArray())); IThrowableProxy[] suppressedThrowableProxies = getSuppressed(ti); if(suppressedThrowableProxies != null) { ThrowableInfo[] suppressed = new ThrowableInfo[suppressedThrowableProxies.length]; for(int i=0;i<suppressedThrowableProxies.length;i++) { suppressed[i] = initFromThrowableProxy(suppressedThrowableProxies[i], calculatePackagingData); } result.setSuppressed(suppressed); } result.setCause(initFromThrowableProxy(ti.getCause(), calculatePackagingData)); return result; } private ExtendedStackTraceElement[] initFromStackTraceElementProxyArray(StackTraceElementProxy[] stackTraceElementProxies) { if(stackTraceElementProxies == null) { return null; } int elementCount = stackTraceElementProxies.length; ExtendedStackTraceElement[] result = new ExtendedStackTraceElement[elementCount]; for(int i = 0; i < elementCount; i++) { StackTraceElementProxy currentInput = stackTraceElementProxies[i]; if(currentInput != null) { ExtendedStackTraceElement current = new ExtendedStackTraceElement(currentInput.getStackTraceElement()); ClassPackagingData cpd = currentInput.getClassPackagingData(); if(cpd != null) { current.setCodeLocation(cpd.getCodeLocation()); current.setExact(cpd.isExact()); current.setVersion(cpd.getVersion()); } result[i] = current; } } return result; } private void initMarker(ch.qos.logback.classic.spi.ILoggingEvent src, LoggingEvent dst) { org.slf4j.Marker origMarker = src.getMarker(); if(origMarker == null) { return; } Map<String, Marker> markers = new HashMap<>(); dst.setMarker(initMarkerRecursive(origMarker, markers)); } private Marker initMarkerRecursive(org.slf4j.Marker origMarker, Map<String, Marker> markers) { if(origMarker == null) { return null; } String name = origMarker.getName(); if(markers.containsKey(name)) { return markers.get(name); } Marker newMarker = new Marker(name); markers.put(name, newMarker); if(origMarker.hasReferences()) { Iterator iter = origMarker.iterator(); while(iter.hasNext()) { org.slf4j.Marker current = (org.slf4j.Marker) iter.next(); newMarker.add(initMarkerRecursive(current, markers)); } } return newMarker; } public LoggingEvent convert(Object o) { if(o == null) { return null; } if(!(o instanceof ch.qos.logback.classic.spi.ILoggingEvent)) { throw new IllegalArgumentException(""+o+" is not a "+getSourceClass()+"!"); } ch.qos.logback.classic.spi.ILoggingEvent event = (ch.qos.logback.classic.spi.ILoggingEvent) o; LoggingEvent result = new LoggingEvent(); String messagePattern = event.getMessage(); Object[] originalArguments = event.getArgumentArray(); MessageFormatter.ArgumentResult argumentResult = MessageFormatter.evaluateArguments(messagePattern, originalArguments); String[] arguments = null; if(argumentResult != null) { arguments = argumentResult.getArguments(); Throwable t = argumentResult.getThrowable(); if(t != null && event.getThrowableProxy() == null && event instanceof ch.qos.logback.classic.spi.LoggingEvent) { ch.qos.logback.classic.spi.LoggingEvent le = (ch.qos.logback.classic.spi.LoggingEvent) event; le.setThrowableProxy(new ThrowableProxy(t)); } } if(messagePattern != null || arguments != null) { Message message = new Message(messagePattern, arguments); result.setMessage(message); } event.prepareForDeferredProcessing(); // TODO: configurable calculation of packaging data? result.setThrowable(initFromThrowableProxy(event.getThrowableProxy(), true)); // TODO: configurable init of call stack, i.e. don't execute next line. result.setCallStack(convert(event.getCallerData())); result.setLogger(event.getLoggerName()); result.setLevel(LoggingEvent.Level.valueOf(event.getLevel().toString())); LoggerContextVO lcv = event.getLoggerContextVO(); if(lcv != null) { String name = lcv.getName(); Map<String, String> props = lcv.getPropertyMap(); if(props != null) { // lcv property map leak? yes, indeed. See http://jira.qos.ch/browse/LBCLASSIC-115 props = new HashMap<>(props); } LoggerContext loggerContext = new LoggerContext(); loggerContext.setName(name); loggerContext.setProperties(props); loggerContext.setBirthTime(lcv.getBirthTime()); result.setLoggerContext(loggerContext); } initMarker(event, result); result.setMdc(event.getMDCPropertyMap()); String threadName = event.getThreadName(); if(threadName != null) { ThreadInfo threadInfo = new ThreadInfo(); threadInfo.setName(threadName); result.setThreadInfo(threadInfo); } result.setTimeStamp(event.getTimeStamp()); return result; } public Class getSourceClass() { return ch.qos.logback.classic.spi.ILoggingEvent.class; } }