001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *      Delbosc Benoit
018 */
019package org.nuxeo.common.logging;
020
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023
024/**
025 * Helper to log information that can be displayed using plantuml to render sequence UML diagram.
026 *
027 * @since 8.1
028 */
029public class SequenceTracer {
030
031    private static final Log log = LogFactory.getLog(SequenceTracer.class);
032    private static final String PREFIX = "@@ ";
033    private static final String DEFAULT_COLOR = "#white";
034    private static final int MESSAGE_MAX_LEN = 250;
035
036    // Utility class.
037    private SequenceTracer() {
038    }
039
040    /**
041     * Mark an event.
042     */
043    public static void mark(String message) {
044        if (!log.isDebugEnabled()) {
045            return;
046        }
047        final String tn = getThreadName();
048        log.debug(PREFIX + tn + " -> " + tn + ": " + message);
049    }
050
051    /**
052     * Mark the beginning of an action
053     */
054    public static void start(String message) {
055        start(message, DEFAULT_COLOR);
056    }
057
058    /**
059     * Mark the beginning of an action
060     */
061    public static void start(String message, String color) {
062        if (!log.isDebugEnabled()) {
063            return;
064        }
065        final String tn = getThreadName();
066        log.debug(PREFIX + tn + " -> " + tn + ": " + format(message) + "\n" + PREFIX + "activate " + tn + " " +
067                color);
068    }
069
070    /**
071     * Mark the beginning of an action initiated by the caller.
072     */
073    public static void startFrom(final String callerThread, final String message) {
074        startFrom(callerThread, message, DEFAULT_COLOR);
075    }
076
077    /**
078     * Mark the beginning of an action initiated by the caller.
079     */
080    public static void startFrom(final String callerThread, final String message, final String color) {
081        if (!log.isDebugEnabled()) {
082            return;
083        }
084        final String tn = getThreadName();
085        log.debug(PREFIX + sanitize(callerThread) + " o--> " + tn + ": Initiate\n" + PREFIX
086                + tn + " -> " + tn + ": " + format(message) + "\n" + PREFIX
087                + "activate " + tn + " " + color);
088    }
089
090    private static String format(String message) {
091        String ret = sanitize(message).replace(", ", ",\\n");
092        ret = insertNewLine(ret);
093        return ret;
094    }
095
096    private static String sanitize(String message) {
097        String ret = message.replace("-", "_").replace(":", "_");
098        if (ret.length() > MESSAGE_MAX_LEN) {
099            ret = ret.substring(0, MESSAGE_MAX_LEN) + "...";
100        }
101        return ret;
102    }
103
104    private static String insertNewLine(String message) {
105        return String.join("\\n", message.split("(?<=\\G.{40})"));
106    }
107
108    /**
109     * Mark the end of the previous action.
110     */
111    public static void stop(String message) {
112        if (!log.isDebugEnabled()) {
113            return;
114        }
115        final String tn = getThreadName();
116        log.debug(PREFIX + tn + " -> " + tn + ": " + format(message) + "\n" + PREFIX + "deactivate " + tn);
117    }
118
119    /**
120     * Mark the last action as failure
121     */
122    public static void destroy(String message) {
123        if (!log.isDebugEnabled()) {
124            return;
125        }
126        final String tn = getThreadName();
127        log.debug(PREFIX + tn + " -> " + tn + ": " + format(message) + "\n" + PREFIX + "destroy " + tn);
128    }
129
130    /**
131     * Add a note on the current thread
132     */
133    public static void addNote(String message) {
134        if (!log.isDebugEnabled()) {
135            return;
136        }
137        log.debug(PREFIX + "note right of " + getThreadName() + ": " + message);
138    }
139
140    /**
141     * Link from source to current thread.
142     */
143    public static void addRelation(String source, String message) {
144        if (!log.isDebugEnabled()) {
145            return;
146        }
147        log.debug(PREFIX + source + " --> " + getThreadName() + ": " + format(message));
148    }
149
150    /**
151     * Get the thread name sanitized for plantuml
152     */
153    public static String getThreadName() {
154        return sanitize(Thread.currentThread().getName());
155    }
156
157    public static boolean isEnabled() {
158        return log.isDebugEnabled();
159    }
160}