Skip to content

Commit

Permalink
add tracer flare (#4351)
Browse files Browse the repository at this point in the history
* add tracer flare

* revert no longer needed change
  • Loading branch information
rochdev authored May 30, 2024
1 parent 2f90a64 commit 557814a
Show file tree
Hide file tree
Showing 8 changed files with 465 additions and 94 deletions.
44 changes: 44 additions & 0 deletions packages/dd-trace/src/flare/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict'

const { Writable } = require('stream')

const INITIAL_SIZE = 64 * 1024

class FlareFile extends Writable {
constructor () {
super()

this.length = 0

this._buffer = Buffer.alloc(INITIAL_SIZE)
}

get data () {
return this._buffer.subarray(0, this.length)
}

_write (chunk, encoding, callback) {
const length = Buffer.byteLength(chunk)

this._reserve(length)

if (Buffer.isBuffer(chunk)) {
this.length += chunk.copy(this._buffer, this.length)
} else {
this.length += this._buffer.write(chunk, encoding)
}

callback()
}

_reserve (length) {
while (this.length + length > this._buffer.length) {
const buffer = Buffer.alloc(this.length * 2)

this._buffer.copy(buffer)
this._buffer = buffer
}
}
}

module.exports = FlareFile
98 changes: 98 additions & 0 deletions packages/dd-trace/src/flare/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use strict'

const log = require('../log')
const startupLog = require('../startup-log')
const FlareFile = require('./file')
const { LogChannel } = require('../log/channels')
const request = require('../exporters/common/request')
const FormData = require('../exporters/common/form-data')

const MAX_LOG_SIZE = 12 * 1024 * 1024 // 12MB soft limit
const TIMEOUT = 20 * 1000 * 60

let logChannel = null
let tracerLogs = null
let timer
let tracerConfig = null

const logger = {
debug: (msg) => recordLog(msg),
info: (msg) => recordLog(msg),
warn: (msg) => recordLog(msg),
error: (err) => recordLog(err.stack)
}

const flare = {
enable (tracerConfig_) {
tracerConfig = tracerConfig_
},

disable () {
tracerConfig = null

flare.cleanup()
},

prepare (logLevel) {
if (!tracerConfig) return

logChannel?.unsubscribe(logger)
logChannel = new LogChannel(logLevel)
logChannel.subscribe(logger)
tracerLogs = tracerLogs || new FlareFile()
timer = timer || setTimeout(flare.cleanup, TIMEOUT)
},

send (task) {
if (!tracerConfig) return

const tracerInfo = new FlareFile()

tracerInfo.write(JSON.stringify(startupLog.tracerInfo(), null, 2))

flare._sendFile(task, tracerInfo, 'tracer_info.txt')
flare._sendFile(task, tracerLogs, 'tracer_logs.txt')

flare.cleanup()
},

cleanup () {
logChannel?.unsubscribe(logger)
timer = clearTimeout(timer)
logChannel = null
tracerLogs = null
},

_sendFile (task, file, filename) {
if (!file) return

const form = new FormData()

form.append('case_id', task.case_id)
form.append('hostname', task.hostname)
form.append('email', task.user_handle)
form.append('source', 'tracer_nodejs')
form.append('flare_file', file.data, { filename })

request(form, {
url: tracerConfig.url,
hostname: tracerConfig.hostname,
port: tracerConfig.port,
method: 'POST',
path: '/tracer_flare/v1',
headers: form.getHeaders()
}, (err) => {
if (err) {
log.error(err)
}
})
}
}

function recordLog (msg) {
if (tracerLogs.length > MAX_LOG_SIZE) return

tracerLogs.write(`${msg}\n`) // TODO: gzip
}

module.exports = flare
83 changes: 54 additions & 29 deletions packages/dd-trace/src/log/channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,69 @@
const { channel } = require('dc-polyfill')

const Level = {
Debug: 'debug',
Info: 'info',
Warn: 'warn',
Error: 'error'
trace: 20,
debug: 20,
info: 30,
warn: 40,
error: 50,
critical: 50,
off: 100
}

const defaultLevel = Level.Debug
const debugChannel = channel('datadog:log:debug')
const infoChannel = channel('datadog:log:info')
const warnChannel = channel('datadog:log:warn')
const errorChannel = channel('datadog:log:error')

// based on: https://github.com/trentm/node-bunyan#levels
const logChannels = {
[Level.Debug]: createLogChannel(Level.Debug, 20),
[Level.Info]: createLogChannel(Level.Info, 30),
[Level.Warn]: createLogChannel(Level.Warn, 40),
[Level.Error]: createLogChannel(Level.Error, 50)
}
const defaultLevel = Level.debug

function createLogChannel (name, logLevel) {
const logChannel = channel(`datadog:log:${name}`)
logChannel.logLevel = logLevel
return logChannel
function getChannelLogLevel (level) {
return level && typeof level === 'string'
? Level[level.toLowerCase().trim()] || defaultLevel
: defaultLevel
}

function getChannelLogLevel (level) {
let logChannel
if (level && typeof level === 'string') {
logChannel = logChannels[level.toLowerCase().trim()] || logChannels[defaultLevel]
} else {
logChannel = logChannels[defaultLevel]
class LogChannel {
constructor (level) {
this._level = getChannelLogLevel(level)
}

subscribe (logger) {
if (Level.debug >= this._level) {
debugChannel.subscribe(logger.debug)
}
if (Level.info >= this._level) {
infoChannel.subscribe(logger.info)
}
if (Level.warn >= this._level) {
warnChannel.subscribe(logger.warn)
}
if (Level.error >= this._level) {
errorChannel.subscribe(logger.error)
}
}

unsubscribe (logger) {
if (debugChannel.hasSubscribers) {
debugChannel.unsubscribe(logger.debug)
}
if (infoChannel.hasSubscribers) {
infoChannel.unsubscribe(logger.info)
}
if (warnChannel.hasSubscribers) {
warnChannel.unsubscribe(logger.warn)
}
if (errorChannel.hasSubscribers) {
errorChannel.unsubscribe(logger.error)
}
}
return logChannel.logLevel
}

module.exports = {
Level,
getChannelLogLevel,
LogChannel,

debugChannel: logChannels[Level.Debug],
infoChannel: logChannels[Level.Info],
warnChannel: logChannels[Level.Warn],
errorChannel: logChannels[Level.Error]
debugChannel,
infoChannel,
warnChannel,
errorChannel
}
56 changes: 7 additions & 49 deletions packages/dd-trace/src/log/writer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use strict'

const { storage } = require('../../../datadog-core')
const { getChannelLogLevel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')

const { LogChannel } = require('./channels')
const defaultLogger = {
debug: msg => console.debug(msg), /* eslint-disable-line no-console */
info: msg => console.info(msg), /* eslint-disable-line no-console */
Expand All @@ -12,7 +11,7 @@ const defaultLogger = {

let enabled = false
let logger = defaultLogger
let logLevel = getChannelLogLevel()
let logChannel = new LogChannel()

function withNoop (fn) {
const store = storage.getStore()
Expand All @@ -23,45 +22,21 @@ function withNoop (fn) {
}

function unsubscribeAll () {
if (debugChannel.hasSubscribers) {
debugChannel.unsubscribe(onDebug)
}
if (infoChannel.hasSubscribers) {
infoChannel.unsubscribe(onInfo)
}
if (warnChannel.hasSubscribers) {
warnChannel.unsubscribe(onWarn)
}
if (errorChannel.hasSubscribers) {
errorChannel.unsubscribe(onError)
}
logChannel.unsubscribe({ debug, info, warn, error })
}

function toggleSubscription (enable) {
function toggleSubscription (enable, level) {
unsubscribeAll()

if (enable) {
if (debugChannel.logLevel >= logLevel) {
debugChannel.subscribe(onDebug)
}
if (infoChannel.logLevel >= logLevel) {
infoChannel.subscribe(onInfo)
}
if (warnChannel.logLevel >= logLevel) {
warnChannel.subscribe(onWarn)
}
if (errorChannel.logLevel >= logLevel) {
errorChannel.subscribe(onError)
}
logChannel = new LogChannel(level)
logChannel.subscribe({ debug, info, warn, error })
}
}

function toggle (enable, level) {
if (level !== undefined) {
logLevel = getChannelLogLevel(level)
}
enabled = enable
toggleSubscription(enabled)
toggleSubscription(enabled, level)
}

function use (newLogger) {
Expand All @@ -73,26 +48,9 @@ function use (newLogger) {
function reset () {
logger = defaultLogger
enabled = false
logLevel = getChannelLogLevel()
toggleSubscription(false)
}

function onError (err) {
if (enabled) error(err)
}

function onWarn (message) {
if (enabled) warn(message)
}

function onInfo (message) {
if (enabled) info(message)
}

function onDebug (message) {
if (enabled) debug(message)
}

function error (err) {
if (typeof err !== 'object' || !err) {
err = String(err)
Expand Down
20 changes: 20 additions & 0 deletions packages/dd-trace/src/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Tracer extends NoopProxy {
this._pluginManager = new PluginManager(this)
this.dogstatsd = new NoopDogStatsDClient()
this._tracingInitialized = false
this._flare = new LazyModule(() => require('./flare'))

// these requires must work with esm bundler
this._modules = {
Expand Down Expand Up @@ -90,6 +91,25 @@ class Tracer extends NoopProxy {
}
this._enableOrDisableTracing(config)
})

rc.on('AGENT_CONFIG', (action, conf) => {
if (!conf?.name?.startsWith('flare-log-level.')) return

if (action === 'unapply') {
this._flare.disable()
} else if (conf.config?.log_level) {
this._flare.enable(config)
this._flare.module.prepare(conf.config.log_level)
}
})

rc.on('AGENT_TASK', (action, conf) => {
if (action === 'unapply' || !conf) return
if (conf.task_type !== 'tracer_flare' || !conf.args) return

this._flare.enable(config)
this._flare.module.send(conf.args)
})
}

if (config.isGCPFunction || config.isAzureFunction) {
Expand Down
Loading

0 comments on commit 557814a

Please sign in to comment.