Skip to content

Commit d69f98a

Browse files
authored
refactor: extract common OTLP logic into base classes (#6659)
* feat: add OTLP metrics proto definitions and reorganize directory - Add metrics.proto and metrics_service.proto (OTLP v1 spec) - Update protobuf_loader to support metrics protos - Rename protos/ -> otlp/ directory for better organization * refactor: extract common OTLP logic into base classes - Create OtlpHttpExporterBase for shared HTTP export logic - Create OtlpTransformerBase for shared transformation logic - Refactor logs exporter/transformer to extend base classes - Update test mocking paths - Eliminates ~400 lines of duplication * fix logs * add support for encoding attributes
1 parent d6c64ca commit d69f98a

File tree

5 files changed

+368
-261
lines changed

5 files changed

+368
-261
lines changed

packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js

Lines changed: 7 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
'use strict'
22

3-
const http = require('http')
4-
const { URL } = require('url')
5-
const log = require('../../log')
3+
const OtlpHttpExporterBase = require('../otlp/otlp_http_exporter_base')
64
const OtlpTransformer = require('./otlp_transformer')
7-
const telemetryMetrics = require('../../telemetry/metrics')
85

96
/**
107
* @typedef {import('@opentelemetry/resources').Resource} Resource
118
* @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
12-
*
13-
*/
14-
15-
const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
9+
*/
1610

1711
/**
1812
* OtlpHttpLogExporter exports log records via OTLP over HTTP.
@@ -21,12 +15,9 @@ const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
2115
* https://opentelemetry.io/docs/specs/otlp/#otlphttp
2216
*
2317
* @class OtlpHttpLogExporter
18+
* @extends OtlpHttpExporterBase
2419
*/
25-
class OtlpHttpLogExporter {
26-
#telemetryTags
27-
28-
DEFAULT_LOGS_PATH = '/v1/logs'
29-
20+
class OtlpHttpLogExporter extends OtlpHttpExporterBase {
3021
/**
3122
* Creates a new OtlpHttpLogExporter instance.
3223
*
@@ -37,28 +28,8 @@ class OtlpHttpLogExporter {
3728
* @param {Resource} resource - Resource attributes
3829
*/
3930
constructor (url, headers, timeout, protocol, resource) {
40-
const parsedUrl = new URL(url)
41-
42-
this.protocol = protocol
31+
super(url, headers, timeout, protocol, '/v1/logs', 'logs')
4332
this.transformer = new OtlpTransformer(resource, protocol)
44-
// If no path is provided, use default path
45-
const path = parsedUrl.pathname === '/' ? this.DEFAULT_LOGS_PATH : parsedUrl.pathname
46-
const isJson = protocol === 'http/json'
47-
this.options = {
48-
hostname: parsedUrl.hostname,
49-
port: parsedUrl.port,
50-
path: path + parsedUrl.search,
51-
method: 'POST',
52-
timeout,
53-
headers: {
54-
'Content-Type': isJson ? 'application/json' : 'application/x-protobuf',
55-
...this.#parseAdditionalHeaders(headers)
56-
}
57-
}
58-
this.#telemetryTags = [
59-
'protocol:http',
60-
`encoding:${isJson ? 'json' : 'protobuf'}`
61-
]
6233
}
6334

6435
/**
@@ -74,99 +45,8 @@ class OtlpHttpLogExporter {
7445
}
7546

7647
const payload = this.transformer.transformLogRecords(logRecords)
77-
this.#sendPayload(payload, resultCallback)
78-
tracerMetrics.count('otel.log_records', this.#telemetryTags).inc(logRecords.length)
79-
}
80-
81-
/**
82-
* Sends the payload via HTTP request.
83-
* @param {Buffer|string} payload - The payload to send
84-
* @param {Function} resultCallback - Callback for the result
85-
* @private
86-
*/
87-
#sendPayload (payload, resultCallback) {
88-
const options = {
89-
...this.options,
90-
headers: {
91-
...this.options.headers,
92-
'Content-Length': payload.length
93-
}
94-
}
95-
96-
const req = http.request(options, (res) => {
97-
let data = ''
98-
99-
res.on('data', (chunk) => {
100-
data += chunk
101-
})
102-
103-
res.on('end', () => {
104-
if (res.statusCode >= 200 && res.statusCode < 300) {
105-
resultCallback({ code: 0 })
106-
} else {
107-
const error = new Error(`HTTP ${res.statusCode}: ${data}`)
108-
resultCallback({ code: 1, error })
109-
}
110-
})
111-
})
112-
113-
req.on('error', (error) => {
114-
log.error('Error sending OTLP logs:', error)
115-
resultCallback({ code: 1, error })
116-
})
117-
118-
req.on('timeout', () => {
119-
req.destroy()
120-
const error = new Error('Request timeout')
121-
resultCallback({ code: 1, error })
122-
})
123-
124-
req.write(payload)
125-
req.end()
126-
}
127-
128-
/**
129-
* Parses additional HTTP headers from a comma-separated string.
130-
* @param {string} headersString - Comma-separated key=value pairs
131-
* @returns {Record<string, string>} Parsed headers object
132-
* @private
133-
*/
134-
#parseAdditionalHeaders (headersString) {
135-
const headers = {}
136-
let key = ''
137-
let value = ''
138-
let readingKey = true
139-
140-
for (const char of headersString) {
141-
if (readingKey) {
142-
if (char === '=') {
143-
readingKey = false
144-
key = key.trim()
145-
} else {
146-
key += char
147-
}
148-
} else if (char === ',') {
149-
value = value.trim()
150-
if (key && value) {
151-
headers[key] = value
152-
}
153-
key = ''
154-
value = ''
155-
readingKey = true
156-
} else {
157-
value += char
158-
}
159-
}
160-
161-
// Add the last pair if present
162-
if (!readingKey) {
163-
value = value.trim()
164-
if (value) {
165-
headers[key] = value
166-
}
167-
}
168-
169-
return headers
48+
this._sendPayload(payload, resultCallback)
49+
this._recordTelemetry('otel.log_records', logRecords.length)
17050
}
17151
}
17252

0 commit comments

Comments
 (0)