Skip to content
This repository has been archived by the owner on Oct 4, 2023. It is now read-only.

Commit

Permalink
move the instrumentation to the audiusBackend
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaylor89 committed Sep 13, 2022
1 parent 7f4a144 commit 128aa9b
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 23 deletions.
10 changes: 10 additions & 0 deletions packages/common/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"dependencies": {
"@audius/sdk": "1.0.1",
"@fingerprintjs/fingerprintjs-pro": "3.5.6",
"@opentelemetry/api": "^1.2.0",
"@opentelemetry/semantic-conventions": "^1.6.0",
"@optimizely/optimizely-sdk": "4.0.0",
"@reduxjs/toolkit": "1.3.4",
"bn.js": "4.11.6",
Expand Down
106 changes: 102 additions & 4 deletions packages/common/src/services/audius-backend/AudiusBackend.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { IdentityAPI, DiscoveryAPI } from '@audius/sdk/dist/core'
import type { HedgehogConfig } from '@audius/sdk/dist/services/hedgehog'
import type { LocalStorage } from '@audius/sdk/dist/utils/localStorage'
import type { Span, SpanOptions, Tracer } from '@opentelemetry/api'
import { SemanticAttributes } from '@opentelemetry/semantic-conventions'
import { ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token'
import {
PublicKey,
Expand Down Expand Up @@ -159,6 +161,88 @@ const combineLists = <Entity extends Track | User>(

const notDeleted = (e: { is_delete: boolean }) => !e.is_delete

/**
* Higher-order function that adds opentelemetry tracing to a function.
* This wrapper works for both sync and async functions
*
* @param {string?} param.name optional name to give to the span, defaults to the function name
* @param {Object?} param.context optional object context to get wrapped, useful when wrapping non-static methods to classes
* @param {TFunction} param.fn the generic function to instrument
* @param {SpanOptions?} param.options objects to pass into the span
* @returns the instrumented function
* @throws rethrows any errors from the original fn
*
* Usage of this would look like
* ```
* const someFunction = instrumentTracing({ fn: _someFunction })
* const result = someFunction(args))
* // or
* const result = await someFunction(args)
* ```
*/
const instrumentTracing = <TFunction extends (...args: any[]) => any>({
fn,
tracer,
name,
context,
options
}: {
fn: TFunction
tracer: Tracer
name?: string
context?: Object
options?: SpanOptions
}) => {
const objectContext = context || this

// build a wrapper around `fn` that accepts the same parameters and returns the same return type
const wrapper = function (
...args: Parameters<TFunction>
): ReturnType<TFunction> {
const spanName = name || fn.name
const spanOptions = options || {}
return tracer.startActiveSpan(spanName, spanOptions, (span: Span) => {
try {
span.setAttribute(SemanticAttributes.CODE_FUNCTION, fn.name)

// TODO add skip parameter to instrument testing function to NOT log certain args
// tracing.setSpanAttribute('args', JSON.stringify(args))
const result = fn.apply(objectContext, args)

// if `fn` is async, await the result
if (result && result.then) {
/**
* by handling promise like this, the caller to this wrapper
* can still use normal async/await syntax to `await` the result
* of this wrapper
* i.e. `const output = await instrumentTracing({ fn: _someFunction })(args)`
*
* based on this package: https://github.com/klny/function-wrapper/blob/master/src/wrapper.js#L25
*/
return result.then((val: any) => {
span.end()
return val
})
}

span.end()

// re-return result from synchronous function
return result
} catch (e: any) {
span.recordException(e)
span.end()

// rethrow any errors
throw e
}
})
}
// copy function name
Object.defineProperty(wrapper, 'name', { value: fn.name })
return wrapper
}

type TransactionReceipt = { blockHash: string; blockNumber: number }

let preloadImageTimer: Timer
Expand Down Expand Up @@ -252,6 +336,7 @@ type AudiusBackendParams = {
web3ProviderUrls: Maybe<string[]>
withEagerOption: WithEagerOption
wormholeConfig: AudiusBackendWormholeConfig
tracer: Tracer
}

export const audiusBackend = ({
Expand Down Expand Up @@ -307,7 +392,8 @@ export const audiusBackend = ({
solBridgeAddress,
solTokenBridgeAddress,
wormholeRpcHosts
}
},
tracer
}: AudiusBackendParams) => {
const { getRemoteVar, waitForRemoteConfig } = remoteConfigInstance

Expand Down Expand Up @@ -3287,9 +3373,21 @@ export const audiusBackend = ({
updateUserLocationTimezone,
updateUserSubscription,
upgradeToCreator,
uploadImage,
uploadTrack,
uploadTrackToCreatorNode,
uploadImage: instrumentTracing({
fn: uploadImage,
context: this,
tracer
}),
uploadTrack: instrumentTracing({
fn: uploadTrack,
context: this,
tracer
}),
uploadTrackToCreatorNode: instrumentTracing({
fn: uploadTrackToCreatorNode,
context: this,
tracer
}),
userNodeUrl,
validateTracksInPlaylist,
waitForLibsInit,
Expand Down
1 change: 1 addition & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@opentelemetry/instrumentation-fetch": "^0.32.0",
"@opentelemetry/instrumentation-user-interaction": "^0.31.0",
"@opentelemetry/instrumentation-xml-http-request": "^0.32.0",
"@opentelemetry/resources": "^1.6.0",
"@opentelemetry/sdk-trace-web": "^1.6.0",
"@optimizely/optimizely-sdk": "4.0.0",
"@project-serum/sol-wallet-adapter": "0.2.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { audiusBackend } from '@audius/common'
import type { AudiusLibs } from '@audius/sdk'
import { tracing } from 'tracer'

import { track } from 'services/analytics'
import {
Expand Down Expand Up @@ -139,5 +140,6 @@ export const audiusBackendInstance = audiusBackend({
solBridgeAddress: process.env.REACT_APP_SOL_BRIDGE_ADDRESS,
solTokenBridgeAddress: process.env.REACT_APP_SOL_TOKEN_BRIDGE_ADDRESS,
wormholeRpcHosts: process.env.REACT_APP_WORMHOLE_RPC_HOSTS
}
},
tracer: tracing.getTracer()
})
66 changes: 48 additions & 18 deletions packages/web/src/tracer.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,52 @@
import type { Tracer } from '@opentelemetry/api'
import { trace } from '@opentelemetry/api'
import { ZoneContextManager } from '@opentelemetry/context-zone'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { registerInstrumentations } from '@opentelemetry/instrumentation'
// import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'
// import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'
// import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction'
// import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'
import {
BatchSpanProcessor,
SimpleSpanProcessor,
ConsoleSpanExporter
} from '@opentelemetry/sdk-trace-base'
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'
import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction'
import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'
import { Resource } from '@opentelemetry/resources'
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
import { SemanticResourceAttributes as ResourceAttributesSC } from '@opentelemetry/semantic-conventions'

import { AudiusWebInstrumentation } from './sdkInstrumentation'
const SERVICE_NAME = 'web-client'

const TRACING_ENABLED = true
const OTEL_COLLECTOR_URL = ''
const OTEL_COLLECTOR_URL =
'https://opentelemetry-collector.staging.audius.co/v1/traces'

/**
* Initializes a tracer for content node as well as registers instrumentions
* for packages that are frequently used
* WARNING: this function should be run before any other imports
* i.e.
* ```
* import { setupTracing } from './tracer'
* setupTracing()
* // all other imports
* import { foo } from 'bar'
* import { isEven } from 'pg'
* ```
*/
export const setupTracing = () => {
// If tracing isn't enabled, we don't set up the trace provider
// or register any instrumentations. This won't cause any errors
// as methods like `span.startActiveTrace()` or `tracing.recordException()`
// will just silently do nothing.
if (!TRACING_ENABLED) return

const provider = new WebTracerProvider()
const provider = new WebTracerProvider({
resource: new Resource({
[ResourceAttributesSC.SERVICE_NAME]: SERVICE_NAME
})
})
const exporter = new OTLPTraceExporter({
url: OTEL_COLLECTOR_URL
})
provider.addSpanProcessor(new BatchSpanProcessor(exporter))
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()))

provider.register({
// Changing default contextManager to use ZoneContextManager - supports asynchronous operations - optional
Expand All @@ -39,11 +56,24 @@ export const setupTracing = () => {
// Registering instrumentations
registerInstrumentations({
instrumentations: [
// new UserInteractionInstrumentation(),
// new XMLHttpRequestInstrumentation(),
// new FetchInstrumentation(),
// new DocumentLoadInstrumentation(),
new AudiusWebInstrumentation()
new UserInteractionInstrumentation(),
new XMLHttpRequestInstrumentation(),
new FetchInstrumentation(),
new DocumentLoadInstrumentation()
]
})
}

export const tracing = {
/**
* A Tracer creates spans containing more information about what is happening
* for a given operation, such as a request in a service.
* Tracers are created from Tracer Providers.
*
* This function fetches the tracer from the application context
* @returns {Tracer} the tracer for content node
*/
getTracer: (): Tracer => {
return trace.getTracer(SERVICE_NAME)
}
}

0 comments on commit 128aa9b

Please sign in to comment.