@primafuture/telemetry-stack 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -195,10 +195,32 @@ curl -sS -X POST http://localhost:4318/v1/logs \
195
195
  \"timeUnixNano\": \"${TS}\",
196
196
  \"severityText\": \"INFO\",
197
197
  \"body\": {\"stringValue\": \"hello from telemetry-stack smoke test\"},
198
- \"attributes\": [{
199
- \"key\": \"log_type\",
200
- \"value\": {\"stringValue\": \"smoke_test\"}
201
- }]
198
+ \"attributes\": [
199
+ {
200
+ \"key\": \"app.value.log_type\",
201
+ \"value\": {\"stringValue\": \"smoke_test\"}
202
+ },
203
+ {
204
+ \"key\": \"app.value.route\",
205
+ \"value\": {\"stringValue\": \"/smoke\"}
206
+ },
207
+ {
208
+ \"key\": \"app.value.count\",
209
+ \"value\": {\"intValue\": \"3\"}
210
+ },
211
+ {
212
+ \"key\": \"app.value.parallel\",
213
+ \"value\": {\"boolValue\": true}
214
+ },
215
+ {
216
+ \"key\": \"app.type.deletedAt\",
217
+ \"value\": {\"stringValue\": \"null\"}
218
+ },
219
+ {
220
+ \"key\": \"app.meta.json\",
221
+ \"value\": {\"stringValue\": \"{\\\"log_type\\\":\\\"smoke_test\\\",\\\"route\\\":\\\"/smoke\\\",\\\"count\\\":3,\\\"parallel\\\":true,\\\"deletedAt\\\":null}\"}
222
+ }
223
+ ]
202
224
  }]
203
225
  }]
204
226
  }]
@@ -208,8 +230,86 @@ curl -sS -X POST http://localhost:4318/v1/logs \
208
230
  Query it from Loki:
209
231
 
210
232
  ```bash
211
- curl -G 'http://localhost:3100/loki/api/v1/query' \
212
- --data-urlencode 'query={service_name="telemetry-smoke-test"}'
233
+ curl -G 'http://localhost:3100/loki/api/v1/query_range' \
234
+ --data-urlencode 'query={loki_service_name="telemetry-smoke-test"}' \
235
+ --data-urlencode 'limit=5'
236
+ ```
237
+
238
+ For Grafana trace-to-logs compatibility, the same log is also indexed by the
239
+ legacy `service_name` label:
240
+
241
+ ```bash
242
+ curl -G 'http://localhost:3100/loki/api/v1/query_range' \
243
+ --data-urlencode 'query={service_name="telemetry-smoke-test"}' \
244
+ --data-urlencode 'limit=5'
245
+ ```
246
+
247
+ The returned log should expose application metadata under structured names such
248
+ as `app_value_log_type`, `app_value_route`, `app_value_count`,
249
+ `app_value_parallel`, `app_type_deletedAt`, and `app_meta_json`.
250
+ OpenTelemetry metadata should be under `otel_*`, for example
251
+ `otel_resource_service_name` and `otel_scope_name`.
252
+
253
+ ## Log Metadata in Loki
254
+
255
+ PrimaFuture telemetry libraries encode application metadata before export. The
256
+ query-friendly view uses `app.value.*` for values, `app.type.*` for type markers
257
+ such as `null` or `undefined`, and `app.meta.json` for the JSON representation.
258
+ Loki displays those dotted names as underscore names such as `app_value_route`.
259
+
260
+ Legacy applications can still log plain metadata such as `count`, `parallel`, or
261
+ `route`. Alloy wraps only those unprefixed legacy fields into `app.*`; it leaves
262
+ already namespaced `app.*` metadata untouched.
263
+
264
+ The stack uses these names:
265
+
266
+ ```text
267
+ app_value_* query-friendly application metadata values
268
+ app_type_* application metadata type markers
269
+ app_meta_json JSON application metadata representation
270
+ app_* legacy application log attributes wrapped by Alloy
271
+ otel_resource_* OpenTelemetry resource attributes
272
+ otel_scope_* OpenTelemetry instrumentation scope attributes
273
+ otel_log_* OpenTelemetry log record fields
274
+ loki_* explicit Loki index labels
275
+ service_name legacy compatibility label for Grafana trace-to-logs
276
+ ```
277
+
278
+ Examples:
279
+
280
+ ```text
281
+ count -> app_value_count
282
+ route -> app_value_route
283
+ deletedAt: null -> app_type_deletedAt = "null"
284
+ metadata JSON -> app_meta_json
285
+ service.name -> otel_resource_service_name
286
+ instrumentation scope -> otel_scope_name
287
+ trace id -> otel_log_trace_id
288
+ ```
289
+
290
+ Loki indexes only the low-cardinality resource labels configured in
291
+ `loki.yaml`, such as `loki_service_name` and the compatibility `service_name`.
292
+ Application and OpenTelemetry detail fields are stored as structured metadata,
293
+ not as index labels. Query them after selecting a stream:
294
+
295
+ ```logql
296
+ {loki_service_name="telemetry-dice-roller-example"}
297
+ | trace_id="0242ac120002"
298
+ | app_value_route="/roll"
299
+ ```
300
+
301
+ Current Loki versions return structured metadata merged into the `stream` labels
302
+ map in `query_range` responses. This is a Loki API behavior: the fields are still
303
+ not index labels. Use `/loki/api/v1/labels` or `/loki/api/v1/series` to see the
304
+ real indexed labels.
305
+
306
+ If a query response is too noisy, reduce returned fields at query time without
307
+ changing stored data:
308
+
309
+ ```logql
310
+ {service_name="telemetry-dice-roller-example"}
311
+ | trace_id="0242ac120002"
312
+ | keep service_name, loki_service_name, trace_id, span_id
213
313
  ```
214
314
 
215
315
  ## Useful URLs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primafuture/telemetry-stack",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Reusable PrimaFuture telemetry stack launcher powered by Docker Compose.",
6
6
  "author": "PrimaFuture.cz s.r.o. <dev@primafuture.cz>",
@@ -1,13 +1,55 @@
1
1
  // Main Alloy pipeline for application telemetry.
2
2
  // It receives OTLP traces, metrics, and logs from apps and routes them to the stack.
3
3
 
4
- // Copy selected log attributes into resource attributes so Loki can index them as labels.
4
+ // Normalize log metadata before Loki stores it as labels/structured metadata.
5
+ // App-provided log attributes stay queryable under app.* and OTel/system data under otel.*.
5
6
  otelcol.processor.transform "loki_labels" {
7
+ error_mode = "ignore"
8
+
6
9
  log_statements {
7
10
  context = "log"
8
11
  statements = [
9
- `set(resource.attributes["log_type"], attributes["log_type"]) where attributes["log_type"] != nil`,
10
- `set(resource.attributes["log.file.path"], attributes["log.file.path"]) where attributes["log.file.path"] != nil`,
12
+ // Copy legacy application log attributes into app.* without wrapping already namespaced app.* fields again.
13
+ `merge_maps(log.cache, log.attributes, "upsert")`,
14
+ `delete_matching_keys(log.cache, "^app[.].*")`,
15
+ `delete_matching_keys(log.cache, "^otel[.].*")`,
16
+ `flatten(log.cache, "app", resolveConflicts=true)`,
17
+ `merge_maps(log.attributes, log.cache, "upsert")`,
18
+ `delete_matching_keys(log.cache, ".*")`,
19
+
20
+ // Copy original resource attributes into otel.resource.* before resource labels are rewritten for Loki.
21
+ `merge_maps(log.cache, resource.attributes, "upsert")`,
22
+ `flatten(log.cache, "otel.resource", resolveConflicts=true)`,
23
+ `merge_maps(log.attributes, log.cache, "upsert")`,
24
+ `delete_matching_keys(log.cache, ".*")`,
25
+
26
+ // Copy instrumentation scope data into otel.scope.*.
27
+ `merge_maps(log.cache, instrumentation_scope.attributes, "upsert")`,
28
+ `flatten(log.cache, "otel.scope.attributes", resolveConflicts=true)`,
29
+ `merge_maps(log.attributes, log.cache, "upsert")`,
30
+ `delete_matching_keys(log.cache, ".*")`,
31
+ `set(log.attributes["otel.scope.name"], instrumentation_scope.name) where instrumentation_scope.name != ""`,
32
+ `set(log.attributes["otel.scope.version"], instrumentation_scope.version) where instrumentation_scope.version != ""`,
33
+
34
+ // Duplicate intrinsic log fields into otel.log.* while leaving native trace/span fields intact for Grafana links.
35
+ `set(log.attributes["otel.log.severity_text"], log.severity_text) where log.severity_text != ""`,
36
+ `set(log.attributes["otel.log.severity_number"], log.severity_number)`,
37
+ `set(log.attributes["otel.log.trace_id"], log.trace_id.string) where log.trace_id.string != ""`,
38
+ `set(log.attributes["otel.log.span_id"], log.span_id.string) where log.span_id.string != ""`,
39
+ `set(log.attributes["otel.log.flags"], log.flags)`,
40
+
41
+ // Build explicit Loki labels from original values. Loki will normalize dots to underscores.
42
+ `set(resource.attributes["loki.service.name"], resource.attributes["service.name"]) where resource.attributes["service.name"] != nil`,
43
+ `set(resource.attributes["loki.log.type"], log.attributes["log_type"]) where log.attributes["log_type"] != nil`,
44
+ `set(resource.attributes["loki.log.type"], log.attributes["value.log_type"]) where resource.attributes["loki.log.type"] == nil and log.attributes["value.log_type"] != nil`,
45
+ `set(resource.attributes["loki.log.type"], log.attributes["app.value.log_type"]) where resource.attributes["loki.log.type"] == nil and log.attributes["app.value.log_type"] != nil`,
46
+ `set(resource.attributes["loki.log.file.path"], log.attributes["log.file.path"]) where log.attributes["log.file.path"] != nil`,
47
+ `set(resource.attributes["loki.log.file.path"], log.attributes["value.log.file.path"]) where resource.attributes["loki.log.file.path"] == nil and log.attributes["value.log.file.path"] != nil`,
48
+ `set(resource.attributes["loki.log.file.path"], log.attributes["app.value.log.file.path"]) where resource.attributes["loki.log.file.path"] == nil and log.attributes["app.value.log.file.path"] != nil`,
49
+
50
+ // Keep only namespaced structured metadata, explicit Loki label resource attributes, and the legacy service.name label.
51
+ `keep_matching_keys(log.attributes, "^(app|otel)[.]")`,
52
+ `keep_matching_keys(resource.attributes, "^(loki[.].*|service[.]name)$")`,
11
53
  ]
12
54
  }
13
55
  output {
@@ -12,7 +12,7 @@ datasources:
12
12
  url: http://prometheus:9090
13
13
  basicAuth: false
14
14
  isDefault: false
15
- version: 1
15
+ version: 2
16
16
  editable: true
17
17
  jsonData:
18
18
  httpMethod: GET
@@ -28,7 +28,7 @@ datasources:
28
28
  url: http://mimir:9009/prometheus
29
29
  basicAuth: false
30
30
  isDefault: false
31
- version: 1
31
+ version: 2
32
32
  editable: true
33
33
  jsonData:
34
34
  httpMethod: GET
@@ -41,7 +41,7 @@ datasources:
41
41
  url: http://loki:3100
42
42
  basicAuth: false
43
43
  isDefault: false
44
- version: 1
44
+ version: 2
45
45
  editable: true
46
46
  jsonData:
47
47
  httpMethod: GET
@@ -66,7 +66,7 @@ datasources:
66
66
  url: http://tempo:3200
67
67
  basicAuth: false
68
68
  isDefault: true
69
- version: 1
69
+ version: 2
70
70
  editable: true
71
71
  apiVersion: 1
72
72
  uid: tempo-streaming-enabled
@@ -77,6 +77,16 @@ datasources:
77
77
  streamingEnabled:
78
78
  search: true
79
79
  metrics: true
80
+ tracesToLogsV2:
81
+ customQuery: false
82
+ datasourceUid: loki
83
+ filterBySpanID: true
84
+ filterByTraceID: true
85
+ spanEndTimeShift: 10s
86
+ spanStartTimeShift: "-10s"
87
+ tags:
88
+ - key: service.name
89
+ value: loki_service_name
80
90
  # This Tempo datasource keeps streaming disabled for links that need stable TraceQL behavior.
81
91
  - name: Tempo (no streaming)
82
92
  type: tempo
@@ -85,7 +95,7 @@ datasources:
85
95
  url: http://tempo:3200
86
96
  basicAuth: false
87
97
  isDefault: false
88
- version: 1
98
+ version: 2
89
99
  editable: true
90
100
  apiVersion: 1
91
101
  uid: tempo-streaming-disabled
@@ -102,4 +112,4 @@ datasources:
102
112
  spanStartTimeShift: "-10s"
103
113
  tags:
104
114
  - key: service.name
105
- value: service_name
115
+ value: loki_service_name
@@ -52,12 +52,16 @@ compactor:
52
52
  # Keep demo logs for 7 days.
53
53
  limits_config:
54
54
  retention_period: 168h
55
+ # Keep synthetic Loki metadata out of the way; Alloy already namespaces these values.
56
+ discover_service_name: []
57
+ discover_log_levels: false
55
58
 
56
59
  # OTLP logs arrive with resource attributes. This list chooses which attributes
57
60
  # Loki indexes as labels; dots are converted to underscores in Loki/Grafana.
58
61
  distributor:
59
62
  otlp_config:
60
63
  default_resource_attributes_as_index_labels:
64
+ - loki.service.name
61
65
  - service.name
62
- - log_type
63
- - log.file.path
66
+ - loki.log.type
67
+ - loki.log.file.path