Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
sjvans committed Oct 25, 2023
1 parent 26c0ba7 commit 4ba6220
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 84 deletions.
28 changes: 18 additions & 10 deletions lib/initializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ const LOG = cds.log('otel')
const fs = require('fs')
const xsenv = require('@sap/xsenv')

const { SpanKind, trace, metrics, diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api')
const { CompositePropagator, W3CTraceContextPropagator, W3CBaggagePropagator } = require('@opentelemetry/core')
const { registerInstrumentations } = require('@opentelemetry/instrumentation')
const { SemanticAttributes, SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions')
const { Resource } = require('@opentelemetry/resources')
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node')
const { B3Propagator, B3InjectEncoding } = require('@opentelemetry/propagator-b3')
const { CompositePropagator, W3CTraceContextPropagator, W3CBaggagePropagator } = require('@opentelemetry/core')
const { Resource } = require('@opentelemetry/resources')
const { SemanticAttributes, SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions')

const {
BatchSpanProcessor,
ConsoleSpanExporter,
Expand All @@ -20,7 +21,8 @@ const {
AlwaysOffSampler,
TraceIdRatioBasedSampler
} = require('@opentelemetry/sdk-trace-base')
const { SpanKind, trace, metrics, diag, DiagConsoleLogger } = require('@opentelemetry/api')
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node')

const {
MeterProvider,
PeriodicExportingMetricReader,
Expand All @@ -34,12 +36,13 @@ const { OTLPMetricExporter: OTLPMetricExporterHttp } = require('@opentelemetry/e
const { OTLPMetricExporter: OTLPMetricExporterProto } = require('@opentelemetry/exporter-metrics-otlp-proto')

const { instrumentations } = require('../index')

const registerMetrics = require('./metrics')
const { CDSConsoleMetricsExporter } = require('./metrics/CDSConsoleMetricsExporter')

const { addInstrumentation } = require('./traces/instrumentation')
const { CDSConsoleExporter } = require('./traces/CDSConsoleExporter')

const { DiagLogLevel } = require('@opentelemetry/api')
function _getDiagLogLevel() {
if (process.env.OTEL_LOG_LEVEL) return DiagLogLevel[process.env.OTEL_LOG_LEVEL.toUpperCase()]
if (LOG._trace) return DiagLogLevel.VERBOSE
Expand Down Expand Up @@ -73,11 +76,12 @@ module.exports = class OTELInitializer {
tracerProvider: provider,
instrumentations: instrumentations
})
if (!cds.env.requires.otel.trace.tracer)
if (!cds.env.requires.otel.trace.tracer) {
cds.env.requires.otel.trace.tracer = trace.getTracer(
cds.env.requires.otel.trace.name,
cds.env.requires.otel.trace.version
)
}
addInstrumentation()

// get the Dynatrace metadata for entity-awareness
Expand All @@ -101,6 +105,7 @@ module.exports = class OTELInitializer {
/** */
}
}

// Add metrics
const metricReader = new PeriodicExportingMetricReader({
exporter: exporters.metrics,
Expand Down Expand Up @@ -235,7 +240,7 @@ module.exports = class OTELInitializer {
*/
static getExporters() {
const result = { trace: null, metrics: null }
// cds.env.requires.otel.trace.exporter allows to customize SpanExporter if needed

if (!cds.env.requires.otel.trace.exporter) {
try {
if (!cds.env.requires.otel.trace.exportOptions) cds.env.requires.otel.trace.exportOptions = {}
Expand Down Expand Up @@ -270,6 +275,8 @@ module.exports = class OTELInitializer {
throw new Error(`Error during initialization of Exporter , ${error}`)
}
}
result.trace = cds.env.requires.otel.trace.exporter

if (!cds.env.requires.otel.metrics.exporter) {
try {
if (!cds.env.requires.otel.metrics.export)
Expand Down Expand Up @@ -344,9 +351,10 @@ module.exports = class OTELInitializer {
throw new Error(`Error during initialization of Exporter , ${error}`)
}
}
result.trace = cds.env.requires.otel.trace.exporter
result.metrics = cds.env.requires.otel.metrics.exporter
LOG.debug('Exporter', result)

LOG._debug && LOG.debug('Exporter', result)

return result
}
}
Expand Down
117 changes: 48 additions & 69 deletions lib/traces/CDSConsoleExporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,54 @@ const LOG = cds.log('otel', { label: 'otel:traces' })

const path = require('path')

const { ExportResultCode, hrTimeToMilliseconds, hrTimeToTimeStamp } = require('@opentelemetry/core')
const { ExportResultCode, hrTimeToMilliseconds /* , hrTimeToTimeStamp */ } = require('@opentelemetry/core')

function span2line(span, hasParent, parentStartTime = 0) {
const _padded = v =>
`${`${v}`.split('.')[0].padStart(4, ' ')}.${(`${v}`.split('.')[1] || '0').padEnd(6, '0').substring(0, 6)}`
let start = 0
if (parentStartTime) {
// REVISIT: using hrTimeToMilliseconds somehow doesn't work
const s = span.startTime[0] - parentStartTime[0]
const ns = span.startTime[1] - parentStartTime[1]
start = Number(`${s}.${ns < 0 ? 1000000000 + ns : ns}`)
}
const duration = hrTimeToMilliseconds(span.duration)
const end = start + duration
const isDb = Object.keys(span.attributes).some(k => k.match(/^db\./))
let result
if (!hasParent) result = ' '
else if (isDb) result = '\n |-'
else result = '\n |- '
result += `${_padded(duration)} ms (${_padded(start)} ms -> ${_padded(end)} ms) - ${span.name}`

// REVISIT: what is this for?
if (span.attributes['code.filepath'] !== undefined) {
if (
path
.normalize(span.attributes['code.filepath'])
.match(new RegExp(path.normalize(cds.env._home).replaceAll('\\', '\\\\'), 'g')) &&
!path.normalize(span.attributes['code.filepath']).match(/node_modules/g)
) {
result += `: .${path
.normalize(span.attributes['code.filepath'])
.substring(
path.normalize(cds.env._home).length + 1,
path.normalize(span.attributes['code.filepath']).length
)}:${span.attributes['code.lineno']}:${span.attributes['code.column']}`
}
}

return result
}

/**
* This is implementation of {@link SpanExporter} that prints spans as single lines to the
* console. This class can be used for diagnostic purposes.
*/

/* eslint-disable no-console */
class CDSConsoleExporter /* implements SpanExporter */ {
_temporaryStorage = new Map()

/**
* Export spans.
* @param spans
Expand All @@ -31,80 +70,20 @@ class CDSConsoleExporter /* implements SpanExporter */ {
return Promise.resolve()
}

/**
* converts span info into more readable format
* @param span
*/
_exportInfoJSON(span) {
return {
traceId: span.spanContext().traceId,
parentId: span.parentSpanId,
traceState: span.spanContext().traceState?.serialize(),
name: span.name,
id: span.spanContext().spanId,
kind: span.kind,
timestamp: hrTimeToTimeStamp(span.startTime),
duration: hrTimeToMilliseconds(span.duration),
attributes: span.attributes,
status: span.status,
events: span.events,
links: span.links
}
}

_temporaryStorage = new Map()

_exportInfoString(span, hasParent, parentStartTime = 0) {
const _padded = v =>
`${`${v}`.split('.')[0].padStart(4, ' ')}.${(`${v}`.split('.')[1] || '0').padEnd(6, '0').substring(0, 6)}`
let start = 0
if (parentStartTime) {
// REVISIT: using hrTimeToMilliseconds somehow doesn't work
const s = span.startTime[0] - parentStartTime[0]
const ns = span.startTime[1] - parentStartTime[1]
start = Number(`${s}.${ns < 0 ? 1000000000 + ns : ns}`)
}
const duration = hrTimeToMilliseconds(span.duration)
const end = start + duration
// let result = `${hasParent ? `\n |-${db ? '-' : ' '} ` : ' '}`
const isDb = Object.keys(span.attributes).some(k => k.match(/^db\./))
let result
if (!hasParent) result = ' '
else if (isDb) result = '\n |-'
else result = '\n |- '
result += `${_padded(duration)} ms (${_padded(start)} ms -> ${_padded(end)} ms) - ${span.name}`
if (span.attributes['code.filepath'] !== undefined) {
if (
path
.normalize(span.attributes['code.filepath'])
.match(new RegExp(path.normalize(cds.env._home).replaceAll('\\', '\\\\'), 'g')) &&
!path.normalize(span.attributes['code.filepath']).match(/node_modules/g)
) {
result += `: .${path
.normalize(span.attributes['code.filepath'])
.substring(
path.normalize(cds.env._home).length + 1,
path.normalize(span.attributes['code.filepath']).length
)}:${span.attributes['code.lineno']}:${span.attributes['code.column']}`
}
}
return result
}

/**
* Showing spans in console
* @param spans
* @param done
*/
_sendSpans(spans, done) {
if (cds.server.url)
if (cds.server.url) {
// Ensures that db init calls during startup are not traced in console
for (const span of spans) {
if (!span.parentSpanId) {
// REVISIT: what is span.attributes['sap.cds.logger']?
// cds default required for express spans
// let toLog = `trace for "${span.attributes['sap.cds.logger'] || 'cds'}": ${this._exportInfoString(span, false)}`
let toLog = this._exportInfoString(span, false)
// let toLog = `trace for "${span.attributes['sap.cds.logger'] || 'cds'}": ${exportInfoString(span, false)}`
let toLog = span2line(span, false)
const furtherLogsToPrint = this._temporaryStorage.get(span.spanContext().traceId)
if (furtherLogsToPrint) {
furtherLogsToPrint
Expand All @@ -113,7 +92,7 @@ class CDSConsoleExporter /* implements SpanExporter */ {
if (d !== 0) return d
return hrTimeToMilliseconds(b.endTime) - hrTimeToMilliseconds(a.endTime) //> the one ending later should be printed first
})
.forEach(t => (toLog += this._exportInfoString(t, true, span.startTime)))
.forEach(t => (toLog += span2line(t, true, span.startTime)))
this._temporaryStorage.delete(span.spanContext().traceId)
}
LOG.info(toLog)
Expand All @@ -123,9 +102,9 @@ class CDSConsoleExporter /* implements SpanExporter */ {
else result.push(span)
}
}
if (done) {
return done({ code: ExportResultCode.SUCCESS })
}

if (done) return done({ code: ExportResultCode.SUCCESS })
}
}

Expand Down
11 changes: 7 additions & 4 deletions lib/traces/instrumentation.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
const trace = require('./trace')
const cds = require('@sap/cds')
const LOG = cds.log('cds')
const APPLOG = cds.log('app')
const DBLOG = cds.log('db|sqlite')

const api = require('@opentelemetry/api')
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions')
const LOG = cds.log('cds'),
APPLOG = cds.log('app'),
DBLOG = cds.log('db|sqlite')

const Service = require('@sap/cds/lib/srv/srv-api')
const ApplicationService = require('@sap/cds/libx/_runtime/cds-services/services/Service')
const DatabaseService = require('@sap/cds/libx/_runtime/db/Service')

const trace = require('./trace')
const wrap = require('./wrapper')

module.exports = {
Expand Down
3 changes: 2 additions & 1 deletion lib/traces/trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const cds = require('@sap/cds')
const LOG = cds.log('otel', { label: 'otel:traces' })

const { locate } = require('func-loc')
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions')

const {
SpanKind,
SpanStatusCode,
Expand All @@ -11,6 +11,7 @@ const {
context: otelContextAPI,
createContextKey: otelCreateContextKey
} = require('@opentelemetry/api')
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions')

/**
* @param {string|object} name
Expand Down

0 comments on commit 4ba6220

Please sign in to comment.