forked from jenkinsci/opentelemetry-plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathOtelLogOutputStream.java
More file actions
83 lines (74 loc) · 3.52 KB
/
OtelLogOutputStream.java
File metadata and controls
83 lines (74 loc) · 3.52 KB
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
package io.jenkins.plugins.opentelemetry.job.log;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.console.LineTransformationOutputStream;
import io.jenkins.plugins.opentelemetry.semconv.ExtendedJenkinsAttributes;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.logs.Severity;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.Clock;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
/**
* Process the output stream and send it to OpenTelemetry.
* TODO support Pipeline Step Context {@link Context} in addition to supporting run root context.
* TODO should we implement a MonotonicallyIncreasedClock to ensure the logs messages are always well sorted? Will backends truncate nano seconds to just do millis and loose this monotonic nature ?
* See https://github.com/jenkinsci/pipeline-cloudwatch-logs-plugin/blob/pipeline-cloudwatch-logs-0.2/src/main/java/io/jenkins/plugins/pipeline_cloudwatch_logs/CloudWatchSender.java#L162
*/
final class OtelLogOutputStream extends LineTransformationOutputStream {
public static boolean ENABLE_LOG_FORMATTING =
Boolean.parseBoolean(System.getProperty("pipeline.log.elastic.enable.log.formatting", "false"));
private static final Logger LOGGER = Logger.getLogger(OtelLogOutputStream.class.getName());
@NonNull
final RunTraceContext runTraceContext;
final io.opentelemetry.api.logs.Logger otelLogger;
final Clock clock;
public OtelLogOutputStream(
@NonNull RunTraceContext runTraceContext,
@NonNull io.opentelemetry.api.logs.Logger otelLogger,
@NonNull Clock clock) {
this.runTraceContext = runTraceContext;
this.otelLogger = otelLogger;
this.clock = clock;
}
@Override
protected void eol(byte[] bytes, int len) {
if (len == 0) {
return;
}
ConsoleNotes.TextAndAnnotations textAndAnnotations = ConsoleNotes.parse(bytes, len);
String plainLogLine = textAndAnnotations.text;
if (plainLogLine == null || plainLogLine.isEmpty()) {
LOGGER.log(Level.FINEST, () -> runTraceContext + " - skip empty log line");
} else {
AttributesBuilder attributesBuilder = Attributes.builder();
if (ENABLE_LOG_FORMATTING && textAndAnnotations.annotations != null) {
attributesBuilder.put(
ExtendedJenkinsAttributes.JENKINS_ANSI_ANNOTATIONS, textAndAnnotations.annotations.toString());
}
attributesBuilder.putAll(runTraceContext.toAttributes());
otelLogger
.logRecordBuilder()
.setSeverity(Severity.INFO)
.setBody(plainLogLine)
.setAllAttributes(attributesBuilder.build())
.setContext(runTraceContext.getContext())
.setTimestamp(clock.now(), TimeUnit.NANOSECONDS)
.emit();
LOGGER.log(
Level.FINEST,
() -> runTraceContext.jobFullName + "#" + runTraceContext.runNumber + " - emit body: '"
+ StringUtils.abbreviate(plainLogLine, 30) + "'");
}
}
@Override
public void flush() {
// there is no flush concept with the Otel Logger
}
@Override
public void close() {
// TODO anything to do? cyrille: probably not
}
}