Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 14 additions & 17 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ const LOG = cds.log('telemetry')
const path = require('path')

const { diag } = require('@opentelemetry/api')
const { getStringFromEnv, diagLogLevelFromString } = require('@opentelemetry/core')
const { registerInstrumentations } = require('@opentelemetry/instrumentation')

const tracing = require('./tracing')
const metrics = require('./metrics')
const logging = require('./logging')
const { getDiagLogLevel, getResource, _require } = require('./utils')
const { getDiagLogLevel, getResource, hasDependency, _require } = require('./utils')

function _getInstrumentations() {
const _instrumentations = cds.env.requires.telemetry.instrumentations
Expand Down Expand Up @@ -59,34 +58,32 @@ function _getInstrumentations() {
return instrumentations
}

module.exports = function () {
function setup_standalone() {
// set logger and propagate log level
diag.setLogger(cds.log('telemetry'), getDiagLogLevel())

// create resource
const resource = getResource()

/*
* setup tracing
*/
// setup tracing, metrics, and logging
const tracerProvider = tracing(resource)

/*
* setup metrics
*/
const meterProvider = metrics(resource)

/*
* setup logging
*/
const loggerProvider = cds.env.requires.telemetry.logging ? logging(resource) : undefined

/*
* register instrumentations
*/
// register instrumentations
registerInstrumentations({
tracerProvider,
meterProvider,
loggerProvider,
instrumentations: _getInstrumentations()
})
}

function setup_with_calm() {
// setup tracing, metrics, and logging
tracing()
metrics()
if (cds.env.requires.telemetry.logging) logging()
}

module.exports = hasDependency('@sap/xotel-agent-ext-js') ? setup_with_calm : setup_standalone
52 changes: 38 additions & 14 deletions lib/logging/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ function _getExporter() {

// for kind telemetry-to-otlp based on env vars
if (loggingExporter === 'env') {
let protocol = getStringFromEnv('OTEL_EXPORTER_OTLP_LOGS_PROTOCOL') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_PROTOCOL')
let protocol =
getStringFromEnv('OTEL_EXPORTER_OTLP_LOGS_PROTOCOL') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_PROTOCOL')
// on kyma, the otlp endpoint speaks grpc, but otel's default protocol is http/protobuf -> fix default
if (!protocol) {
const endpoint = (getStringFromEnv('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_ENDPOINT') ?? '')
const endpoint =
getStringFromEnv('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_ENDPOINT') ?? ''
if (endpoint.match(/:4317/)) protocol = 'grpc'
}
protocol ??= (getStringFromEnv('OTEL_EXPORTER_OTLP_LOGS_PROTOCOL') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_PROTOCOL'))
protocol ??= getStringFromEnv('OTEL_EXPORTER_OTLP_LOGS_PROTOCOL') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_PROTOCOL')
loggingExporter = { module: _protocol2module[protocol], class: 'OTLPLogExporter' }
}

Expand Down Expand Up @@ -73,19 +75,11 @@ module.exports = resource => {

const { logs, SeverityNumber } = require('@opentelemetry/api-logs')
const { LoggerProvider, BatchLogRecordProcessor, SimpleLogRecordProcessor } = require('@opentelemetry/sdk-logs')

const exporter = _getExporter()
const logProcessor = _getCustomProcessor(exporter) ||
(process.env.NODE_ENV === 'production'
? new BatchLogRecordProcessor(exporter)
: new SimpleLogRecordProcessor(exporter))

// TODO: CALM may have initialized a global provider already

const loggerProvider = new LoggerProvider({ resource, processors: [logProcessor]})
logs.setGlobalLoggerProvider(loggerProvider)

// setup logs interception via cds.log.format
cds.on('served', () => {
const loggerProvider = logs.getLoggerProvider()

const loggers = {}
const l2s = { 1: 'ERROR', 2: 'WARN', 3: 'INFO', 4: 'DEBUG', 5: 'TRACE' }

Expand Down Expand Up @@ -132,5 +126,35 @@ module.exports = resource => {
for (const each in cds.log.loggers) cds.log.loggers[each].setFormat(format)
})

/*
* create processor
*/
const exporter = _getExporter()
const processor =
_getCustomProcessor(exporter) ||
(process.env.NODE_ENV === 'production'
? new BatchLogRecordProcessor(exporter)
: new SimpleLogRecordProcessor(exporter))

/*
* either add processor as delegate in CALM...
*/
if (!resource) {
LOG.warn("@sap/xotel-agent-ext-js found, adding @cap-js/telemetry's log processor as delegate")
try {
const { getCompositeLogRecordProcessor } = require('@sap/xotel-agent-ext-js')
getCompositeLogRecordProcessor().addDelegate(processor)
return
} catch (error) {
LOG.error('Failed to add log processor as delegate:', error)
throw error
}
}

/*
* ... or initialize and return provider
*/
const loggerProvider = new LoggerProvider({ resource, processors: [processor] })
logs.setGlobalLoggerProvider(loggerProvider)
return loggerProvider
}
109 changes: 63 additions & 46 deletions lib/metrics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const {
const { getDynatraceMetadata, getCredsForDTAsUPS, getCredsForCLSAsUPS, augmentCLCreds, _require } = require('../utils')

const _protocol2module = {
'grpc': '@opentelemetry/exporter-metrics-otlp-grpc',
grpc: '@opentelemetry/exporter-metrics-otlp-grpc',
'http/protobuf': '@opentelemetry/exporter-metrics-otlp-proto',
'http/json': '@opentelemetry/exporter-metrics-otlp-http'
}
Expand All @@ -26,53 +26,57 @@ function _getExporter() {
credentials
} = cds.env.requires.telemetry

if (metricsExporter === 'env') { // ... process env to determine exporter module to use
let protocol = getStringFromEnv('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_PROTOCOL')

if (metricsExporter === 'env') {
// ... process env to determine exporter module to use
let protocol =
getStringFromEnv('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_PROTOCOL')

if (!protocol) {
// > On kyma, the otlp endpoint speaks grpc, but otel's default protocol is http/protobuf -> fix default
const endpoint = (getStringFromEnv('OTEL_EXPORTER_OTLP_METRICS_ENDPOINT') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_ENDPOINT') ?? '')
const endpoint =
getStringFromEnv('OTEL_EXPORTER_OTLP_METRICS_ENDPOINT') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_ENDPOINT') ?? ''
if (endpoint.match(/:4317/)) protocol = 'grpc'
}

protocol ??= (getStringFromEnv('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_PROTOCOL'))
protocol ??=
getStringFromEnv('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL') ?? getStringFromEnv('OTEL_EXPORTER_OTLP_PROTOCOL')
metricsExporter = { module: _protocol2module[protocol], class: 'OTLPMetricExporter' }
}

// Import the configured exporter module > use _require for better error message
const metricsExporterModule = metricsExporter.module === '@cap-js/telemetry'
? require('../exporter')
: _require(metricsExporter.module)
const metricsExporterModule =
metricsExporter.module === '@cap-js/telemetry' ? require('../exporter') : _require(metricsExporter.module)
if (!metricsExporterModule[metricsExporter.class])
throw new Error(`Unknown metrics exporter "${metricsExporter.class}" in module "${metricsExporter.module}"`)

const config = { ...(metricsExporter.config || {}) }
config.temporalityPreference ??= AggregationTemporality.DELTA

// Augment configruation depending on 'kind' of telementry
if (kind.match(/to-dynatrace$/)) {
if (!credentials) credentials = getCredsForDTAsUPS()
if (!credentials) throw new Error('No Dynatrace credentials found.')

config.url ??= `${credentials.apiurl}/v2/otlp/v1/metrics`
config.headers ??= {}

// Extract REST API token from credentials to configure auth:
// > 'metrics_apitoken' for compatibility with previous releases
// > 'credentials.rest_apitoken?.token' is deprecated and only supported for compatibility reasons
const { token_name } = cds.env.requires.telemetry
const token = credentials[token_name] || credentials.metrics_apitoken || credentials.rest_apitoken?.token
if (!token) throw new Error(`Neither "${token_name}" nor deprecated "rest_apitoken.token" found in Dynatrace credentials`)

if (!token)
throw new Error(`Neither "${token_name}" nor deprecated "rest_apitoken.token" found in Dynatrace credentials`)

config.headers.authorization ??= `Api-Token ${token}`
}

if (kind.match(/to-cloud-logging$/)) {
if (!credentials) credentials = getCredsForCLSAsUPS()
if (!credentials) throw new Error('No SAP Cloud Logging credentials found.')

augmentCLCreds(credentials)

config.url ??= credentials.url
config.credentials ??= credentials.credentials
}
Expand All @@ -86,45 +90,58 @@ module.exports = resource => {
if (!cds.env.requires.telemetry.metrics?.exporter) return

/*
* general setup
* add individual metrics
*/
require('./db-pool')()
require('./queue')()
require('./host')()

/*
* create reader
*/
const metricsConfig = cds.env.requires.telemetry.metrics.config
let exporter = _getExporter()

if (typeof exporter.export === 'function') {
let reader = _getExporter()
if (typeof reader.export === 'function') {
// In case export is a function to be called by this runtime (push):
// > The exporter needs to be wrappeed thus, to set an export interval
exporter = new PeriodicExportingMetricReader({ ...metricsConfig, exporter })
reader = new PeriodicExportingMetricReader({ ...metricsConfig, exporter: reader })
}

const dtmetadata = getDynatraceMetadata();
resource = resourceFromAttributes({}).merge(resource).merge(dtmetadata);
// unfortunately, we have to pass views to the MeterProvider constructor
// something like meterProvider.addView() would be a lot nicer for locality
let views = [];
if (process.env.HOST_METRICS_RETAIN_SYSTEM) {
// nothing to do
} else {
views.push({
meterName: "@cap-js/telemetry:host-metrics",
instrumentName: "system.*",
aggregation: {
type: AggregationType.DROP,
},
});
/*
* either add reader as delegate in CALM...
*/
if (!resource) {
LOG.warn("@sap/xotel-agent-ext-js found, adding @cap-js/telemetry's metric reader as delegate")
try {
const { getCompositeMetricReader } = require('@sap/xotel-agent-ext-js')
getCompositeMetricReader().addDelegate(reader)
return
} catch (error) {
LOG.error('Failed to add metric reader as delegate:', error)
throw error
}

// TODO: CALM may have initialized a global provider already

const meterProvider = new MeterProvider({ resource, readers: [exporter], views });
metrics.setGlobalMeterProvider(meterProvider);
}

/*
* add individual metrics
* ... or initialize and return provider
*/
require('./db-pool')()
require('./queue')()
require('./host')()

const dtmetadata = getDynatraceMetadata()
resource = resourceFromAttributes({}).merge(resource).merge(dtmetadata)
// unfortunately, we have to pass views to the MeterProvider constructor
// something like meterProvider.addView() would be a lot nicer for locality
let views = []
if (process.env.HOST_METRICS_RETAIN_SYSTEM) {
// nothing to do
} else {
views.push({
meterName: '@cap-js/telemetry:host-metrics',
instrumentName: 'system.*',
aggregation: {
type: AggregationType.DROP
}
})
}
const meterProvider = new MeterProvider({ resource, readers: [reader], views })
metrics.setGlobalMeterProvider(meterProvider)
return meterProvider
}
1 change: 0 additions & 1 deletion lib/metrics/queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ module.exports = () => {
const queuedServiceName = req.data.target

if (!registeredServics.has(queuedServiceName)) {

const targetedService = cds.services[queuedServiceName]
if (!targetedService) {
LOG.debug('Skipping registration of queue metrics collection for unknown service:', queuedServiceName)
Expand Down
Loading
Loading