Skip to content

Commit d71013f

Browse files
authored
Merge pull request #1018 from iRevive/core-logs/log-record-builder
core-logs: add LogRecordBuilder
2 parents 7c2edc1 + 342f5ba commit d71013f

File tree

1 file changed

+221
-0
lines changed

1 file changed

+221
-0
lines changed
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
* Copyright 2025 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.typelevel.otel4s.logs
18+
19+
import cats.Applicative
20+
import cats.Monad
21+
import org.typelevel.otel4s.AnyValue
22+
import org.typelevel.otel4s.Attribute
23+
import org.typelevel.otel4s.KindTransformer
24+
import org.typelevel.otel4s.meta.InstrumentMeta
25+
26+
import java.time.Instant
27+
import scala.collection.immutable
28+
import scala.concurrent.duration.FiniteDuration
29+
30+
/** Provides a way to build and emit a log record.
31+
*
32+
* @see
33+
* [[https://opentelemetry.io/docs/specs/otel/logs/api/#emit-a-logrecord]]
34+
*
35+
* @see
36+
* [[https://opentelemetry.io/docs/specs/otel/logs/data-model/]]
37+
*/
38+
trait LogRecordBuilder[F[_], Ctx] {
39+
40+
/** The instrument's metadata. Indicates whether instrumentation is enabled.
41+
*/
42+
def meta: InstrumentMeta.Dynamic[F]
43+
44+
/** Sets the time when the event occurred measured by the origin clock, i.e. the time at the source.
45+
*
46+
* @note
47+
* on multiple subsequent calls, the value from the last call will be retained
48+
*/
49+
def withTimestamp(timestamp: FiniteDuration): LogRecordBuilder[F, Ctx]
50+
51+
/** Sets the time when the event occurred measured by the origin clock, i.e. the time at the source.
52+
*
53+
* @note
54+
* on multiple subsequent calls, the value from the last call will be retained
55+
*/
56+
def withTimestamp(timestamp: Instant): LogRecordBuilder[F, Ctx]
57+
58+
/** Sets the time when the event was observed by the collection system.
59+
*
60+
* @note
61+
* on multiple subsequent calls, the value from the last call will be retained
62+
*/
63+
def withObservedTimestamp(timestamp: FiniteDuration): LogRecordBuilder[F, Ctx]
64+
65+
/** Sets the time when the event was observed by the collection system.
66+
*
67+
* @note
68+
* on multiple subsequent calls, the value from the last call will be retained
69+
*/
70+
def withObservedTimestamp(timestamp: Instant): LogRecordBuilder[F, Ctx]
71+
72+
/** Sets the context to associate with the log record.
73+
*
74+
* The context will be used to extract tracing information, such as trace id and span id.
75+
*
76+
* @note
77+
* on multiple subsequent calls, the value from the last call will be retained
78+
*/
79+
def withContext(context: Ctx): LogRecordBuilder[F, Ctx]
80+
81+
/** Sets the [[Severity]] level.
82+
*
83+
* @note
84+
* on multiple subsequent calls, the value from the last call will be retained
85+
*/
86+
def withSeverity(severity: Severity): LogRecordBuilder[F, Ctx]
87+
88+
/** Sets the severity text (also known as log level) to the builder.
89+
*
90+
* This is the original string representation of the severity as it is known at the source.
91+
*
92+
* @note
93+
* on multiple subsequent calls, the value from the last call will be retained
94+
*/
95+
def withSeverityText(severityText: String): LogRecordBuilder[F, Ctx]
96+
97+
/** Sets the given body to the builder.
98+
*
99+
* Can be, for example, a human-readable string message (including multi-line) describing the event in a free form,
100+
* or it can be a structured data composed of arrays and maps of other values.
101+
*
102+
* @note
103+
* on multiple subsequent calls, the value from the last call will be retained.
104+
*
105+
* @example
106+
* {{{
107+
* val builder: LogRecordBuilder[F] = ???
108+
* builder.withBody(AnyValue.string("the log message"))
109+
* }}}
110+
*/
111+
def withBody(body: AnyValue): LogRecordBuilder[F, Ctx]
112+
113+
/** Sets the event name, which identifies the class or type of the event.
114+
*
115+
* This name should uniquely identify the event structure (both attributes and body).
116+
*
117+
* @note
118+
* on multiple subsequent calls, the value from the last call will be retained
119+
*/
120+
def withEventName(eventName: String): LogRecordBuilder[F, Ctx]
121+
122+
/** Adds the given attribute to the builder.
123+
*
124+
* @note
125+
* if the builder previously contained a mapping for the key, the old value is replaced by the specified value
126+
*/
127+
def addAttribute[A](attribute: Attribute[A]): LogRecordBuilder[F, Ctx]
128+
129+
/** Adds attributes to the builder.
130+
*
131+
* @note
132+
* if the builder previously contained a mapping for any of the keys, the old values are replaced by the specified
133+
* values
134+
*/
135+
def addAttributes(attributes: Attribute[_]*): LogRecordBuilder[F, Ctx]
136+
137+
/** Adds attributes to the builder.
138+
*
139+
* @note
140+
* if the builder previously contained a mapping for any of the keys, the old values are replaced by the specified
141+
* values
142+
*/
143+
def addAttributes(attributes: immutable.Iterable[Attribute[_]]): LogRecordBuilder[F, Ctx]
144+
145+
/** Creates a log record and emits it to the processing pipeline.
146+
*/
147+
def emit: F[Unit]
148+
149+
def mapK[G[_]](implicit G: Monad[G], kt: KindTransformer[F, G]): LogRecordBuilder[G, Ctx] =
150+
new LogRecordBuilder.MappedK(this)
151+
152+
}
153+
154+
object LogRecordBuilder {
155+
156+
def noop[F[_]: Applicative, Ctx]: LogRecordBuilder[F, Ctx] =
157+
new LogRecordBuilder[F, Ctx] {
158+
val meta: InstrumentMeta.Dynamic[F] = InstrumentMeta.Dynamic.disabled
159+
def withTimestamp(timestamp: FiniteDuration): LogRecordBuilder[F, Ctx] = this
160+
def withTimestamp(timestamp: Instant): LogRecordBuilder[F, Ctx] = this
161+
def withObservedTimestamp(timestamp: FiniteDuration): LogRecordBuilder[F, Ctx] = this
162+
def withObservedTimestamp(timestamp: Instant): LogRecordBuilder[F, Ctx] = this
163+
def withContext(context: Ctx): LogRecordBuilder[F, Ctx] = this
164+
def withSeverity(severity: Severity): LogRecordBuilder[F, Ctx] = this
165+
def withSeverityText(severityText: String): LogRecordBuilder[F, Ctx] = this
166+
def withBody(body: AnyValue): LogRecordBuilder[F, Ctx] = this
167+
def withEventName(eventName: String): LogRecordBuilder[F, Ctx] = this
168+
def addAttribute[A](attribute: Attribute[A]): LogRecordBuilder[F, Ctx] = this
169+
def addAttributes(attributes: Attribute[_]*): LogRecordBuilder[F, Ctx] = this
170+
def addAttributes(attributes: immutable.Iterable[Attribute[_]]): LogRecordBuilder[F, Ctx] = this
171+
def emit: F[Unit] = Applicative[F].unit
172+
}
173+
174+
private final class MappedK[F[_], G[_]: Monad, Ctx](
175+
builder: LogRecordBuilder[F, Ctx]
176+
)(implicit kt: KindTransformer[F, G])
177+
extends LogRecordBuilder[G, Ctx] {
178+
179+
val meta: InstrumentMeta.Dynamic[G] = builder.meta.mapK[G]
180+
181+
def withTimestamp(timestamp: FiniteDuration): LogRecordBuilder[G, Ctx] =
182+
builder.withTimestamp(timestamp).mapK
183+
184+
def withTimestamp(timestamp: Instant): LogRecordBuilder[G, Ctx] =
185+
builder.withTimestamp(timestamp).mapK
186+
187+
def withObservedTimestamp(timestamp: FiniteDuration): LogRecordBuilder[G, Ctx] =
188+
builder.withObservedTimestamp(timestamp).mapK
189+
190+
def withObservedTimestamp(timestamp: Instant): LogRecordBuilder[G, Ctx] =
191+
builder.withObservedTimestamp(timestamp).mapK
192+
193+
def withContext(context: Ctx): LogRecordBuilder[G, Ctx] =
194+
builder.withContext(context).mapK
195+
196+
def withSeverity(severity: Severity): LogRecordBuilder[G, Ctx] =
197+
builder.withSeverity(severity).mapK
198+
199+
def withSeverityText(severityText: String): LogRecordBuilder[G, Ctx] =
200+
builder.withSeverityText(severityText).mapK
201+
202+
def withBody(body: AnyValue): LogRecordBuilder[G, Ctx] =
203+
builder.withBody(body).mapK
204+
205+
def withEventName(eventName: String): LogRecordBuilder[G, Ctx] =
206+
builder.withEventName(eventName).mapK
207+
208+
def addAttribute[A](attribute: Attribute[A]): LogRecordBuilder[G, Ctx] =
209+
builder.addAttribute(attribute).mapK
210+
211+
def addAttributes(attributes: Attribute[_]*): LogRecordBuilder[G, Ctx] =
212+
builder.addAttributes(attributes).mapK
213+
214+
def addAttributes(attributes: immutable.Iterable[Attribute[_]]): LogRecordBuilder[G, Ctx] =
215+
builder.addAttributes(attributes).mapK
216+
217+
def emit: G[Unit] =
218+
kt.liftK(builder.emit)
219+
}
220+
221+
}

0 commit comments

Comments
 (0)