summaryrefslogtreecommitdiffstats
path: root/vespalog/src/main/java/com/yahoo/log/VespaFormat.java
blob: 3d1a2ae0dc8742287bbd5abdde110c4b5f4f2f23 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.log;

import java.time.Instant;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Vespa log formatting utility methods.
 * Contains some code based on Util.java in Cloudname https://github.com/Cloudname/cloudname
 * written by Bjørn Borud, licensed under the Apache 2.0 license.
 * 
 * @author arnej27959
 */
public class VespaFormat {

    private static final Pattern special   = Pattern.compile("[\r\\\n\\\t\\\\]+");
    private static final Pattern newLine   = Pattern.compile("\n");
    private static final Pattern carriage  = Pattern.compile("\r");
    private static final Pattern tab       = Pattern.compile("\t");
    private static final Pattern backSlash = Pattern.compile("\\\\");

    private static final String hostname;
    private static final String processID;

    static {
        hostname = Util.getHostName();
        processID = Util.getPID();
    }

    /**
     * This static method is used to detect if a message needs
     * to be escaped, and if so, performs the escaping.  Since the
     * common case is most likely that escaping is <em>not</em>
     * needed, the code is optimized for this case.  The forbidden
     * characters are:
     *
     * <UL>
     *  <LI> newline
     *  <LI> tab
     *  <LI> backslash
     * </UL>
     *
     * <P>
     * Also handles the case where the message is <code>null</code>
     * and replaces the null message with a tag saying that the
     * value was "(empty)".
     *
     * @param s String that might need escaping
     * @return Returns escaped string
     *
     */
    public static String escape (String s) {
        if (s == null) {
            return "(empty)";
        }

        Matcher m = special.matcher(s);
        if (! m.find()) {
            return s;
        }

        // invariant: we had special characters

        m = backSlash.matcher(s);
        if (m.find()) {
            s = m.replaceAll("\\\\\\\\");
        }

        m = newLine.matcher(s);
        if (m.find()) {
            s = m.replaceAll("\\\\n");
        }

        m = carriage.matcher(s);
        if (m.find()) {
            s = m.replaceAll("\\\\r");
        }

        m = tab.matcher(s);
        if (m.find()) {
            s = m.replaceAll("\\\\t");
        }

        return s;
    }


    /**
     * It is easier to slice and dice strings in Java than formatting
     * numbers...
     */
    public static void formatTime (long time, StringBuilder sbuffer) {
        String timeString = Long.toString(time);
        int len = timeString.length();

        // something wrong.  handle it by just returning the input
        // long as a string.  we prefer this to just crashing in
        // the substring handling.
        if (len < 3) {
            sbuffer.append(timeString);
            return;
        }
        sbuffer.append(timeString.substring(0, len - 3));
        sbuffer.append('.');
        sbuffer.append(timeString.substring(len - 3));
    }

    static String formatTime(Instant instant) {
        StringBuilder builder = new StringBuilder();
        VespaFormat.formatTime(instant.toEpochMilli(), builder);
        return builder.toString();
    }

    public static String format(String levelName,
                                String component,
                                String componentPrefix,
                                long millis,
                                String threadId,
                                String serviceName,
                                String formattedMessage,
                                Throwable t)
    {
        StringBuilder sbuf = new StringBuilder(300); // initial guess

        // format the time
        formatTime(millis, sbuf);
        sbuf.append("\t");

        sbuf.append(hostname).append("\t");

        sbuf.append(processID);
        if (threadId != null) {
            sbuf.append("/").append(threadId);
        }
        sbuf.append("\t");

        sbuf.append(serviceName).append("\t");

        if (component == null && componentPrefix == null) {
            sbuf.append("-");
        } else if (component == null) {
            sbuf.append(componentPrefix);
        } else if (componentPrefix == null) {
            sbuf.append(".").append(component);
        } else {
            sbuf.append(componentPrefix).append(".").append(component);
        }
        sbuf.append("\t");

        sbuf.append(levelName).append("\t");

        sbuf.append(escape(formattedMessage));
        if (t != null) {
            formatException(t, sbuf);
        }
        sbuf.append("\n");
        return sbuf.toString();
    }

    /**
     * Format throwable into given StringBuffer.
     *
     * @param t The Throwable we want to format
     * @param sbuf The stringbuffer into which we wish to
     *             format the Throwable
     */
    public static void formatException (Throwable t, StringBuilder sbuf) {
        Throwable last = t;
        int depth = 0;
        while (last != null) {
            sbuf.append("\\nmsg=\"");
            sbuf.append(escape(last.getMessage()));
            sbuf.append("\"\\nname=\"");
            sbuf.append(escape(last.getClass().getName()));
            sbuf.append("\"\\nstack=\"\\n");

            // loop through stack frames and format them
            StackTraceElement[] st = last.getStackTrace();
            int stopAt = Math.min(st.length, 15);
            boolean first = true;
            for (int i = 0; i < stopAt; i++) {
                if (first) {
                    first = false;
                } else {
                    sbuf.append("\\n");
                }
                sbuf.append(escape(st[i].toString()));
            }

            // tell the reader if we chopped off part of the stacktrace
            if (stopAt < st.length) {
                sbuf.append("\\n[...]");
            }
            sbuf.append("\\n\"");

            last = last.getCause();
            depth++;
        }
        sbuf.append(" nesting=").append(depth);
    }

    static String formatThreadProcess(long processId, long threadId) {
        if (threadId == 0) {
            return Long.toString(processId);
        }
        return processId + "/" + threadId;
    }

}