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

Commit

Permalink
feat: update APM instrumentation (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastiendan authored Sep 26, 2023
1 parent 27001a0 commit 477fde4
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 154 deletions.
11 changes: 11 additions & 0 deletions packages/backend/src/apm/apm.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'

import { ApmService } from './apm.service'

@Module({
imports: [ConfigModule],
exports: [ApmService],
providers: [ApmService],
})
export class ApmModule {}
39 changes: 39 additions & 0 deletions packages/backend/src/apm/apm.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import apm, * as ElasticAPM from 'elastic-apm-node'

@Injectable()
export class ApmService {
private _apm: ElasticAPM.Agent

constructor(private configService: ConfigService) {
this._apm = ElasticAPM.start({
serviceName:
this.configService.get('TRACING_SERVICE_NAME') || 'faucet-server',
secretToken: this.configService.get('ELASTIC_APM_TOKEN') || '',
serverUrl: this.configService.get('ELASTIC_APM_ENDPOINT') || '',
environment:
this.configService.get('TRACING_SERVICE_VERSION') || 'unknown',
captureBody: 'all',
})
}

startTransaction(name: string, traceparent?: string) {
return this._apm.startTransaction(
name,
traceparent
? {
childOf: traceparent,
}
: undefined
)
}

startChildSpan(name: string) {
return this._apm.startSpan(name)
}

captureError(error: string) {
this._apm.captureError(error)
}
}
2 changes: 2 additions & 0 deletions packages/backend/src/faucet/faucet.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Module } from '@nestjs/common'

import { ApmModule } from '../apm/apm.module'
import { FaucetController } from './faucet.controller'
import { FaucetService } from './faucet.service'

@Module({
controllers: [FaucetController],
imports: [ApmModule],
providers: [FaucetService],
})
export class FaucetModule {}
71 changes: 42 additions & 29 deletions packages/backend/src/faucet/faucet.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,65 @@ import { ConfigService } from '@nestjs/config'
import { ethers, providers } from 'ethers'

import { GetSubnetAssetsDto } from './faucet.dto'
import { apm } from '../main'
import { sanitizeURLProtocol } from '../utils'
import { PROVIDER_ERRORS, WALLET_ERRORS } from './faucet.errors'
import { Transaction } from 'elastic-apm-node'
import { ApmService } from 'src/apm/apm.service'

@Injectable()
export class FaucetService {
constructor(private configService: ConfigService) {}
constructor(
private configService: ConfigService,
private apmService: ApmService
) {}

async getSubnetAssets(
{ address, subnetEndpoints }: GetSubnetAssetsDto,
traceparent?: string
) {
const apmTransaction = apm.startTransaction('root-server', {
childOf: traceparent,
const apmTransaction = this.apmService.startTransaction(
'faucetService.getSubnetAssets',
traceparent
)

apmTransaction.addLabels({
address,
subnetEndpoints: JSON.stringify(subnetEndpoints),
})

return Promise.all(
subnetEndpoints.map((endpoint, index) =>
this._sendTransaction(address, endpoint, {
apmSpanIndex: index,
apmTransaction,
const promises = []

for (const endpoint of subnetEndpoints) {
promises.push(
new Promise(async (resolve, reject) => {
try {
const { logsBloom, ...receipt } = await this._sendTransaction(
address,
endpoint
)
apmTransaction.addLabels({
[`receipt-${endpoint.replace('.', '-')}`]:
JSON.stringify(receipt),
})
resolve(receipt)
} catch (error) {
this.apmService.captureError(error)
reject(error)
}
})
)
).finally(() => {
apmTransaction.end()
})
}

const receipts = await Promise.all(promises)

apmTransaction.end()

return receipts
}

private async _sendTransaction(
address: string,
subnetEndpoint: string,
{
apmSpanIndex,
apmTransaction,
}: { apmSpanIndex: number; apmTransaction: Transaction }
) {
private async _sendTransaction(address: string, subnetEndpoint: string) {
return new Promise<providers.TransactionReceipt>(
async (resolve, reject) => {
try {
const span = apmTransaction.startSpan(
`get-subnet-asset-${apmSpanIndex}`
)
span.addLabels({ address, subnetEndpoint })

const provider = await this._createProvider(subnetEndpoint)

const wallet = this._createWallet(provider)
Expand All @@ -58,9 +72,6 @@ export class FaucetService {
})

const receipt = await tx.wait()

span.end()

resolve(receipt)
} catch (error) {
reject(error)
Expand All @@ -83,6 +94,7 @@ export class FaucetService {
provider.on('debug', (data) => {
if (data.error) {
clearTimeout(timeoutId)
this.apmService.captureError(data.error)
reject(new Error(PROVIDER_ERRORS.INVALID_ENDPOINT))
}
})
Expand All @@ -96,6 +108,7 @@ export class FaucetService {
provider
)
} catch (error) {
this.apmService.captureError(error)
throw new Error(WALLET_ERRORS.INVALID_PRIVATE_KEY)
}
}
Expand Down
15 changes: 0 additions & 15 deletions packages/backend/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
import { ValidationPipe } from '@nestjs/common'
import { Logger } from '@nestjs/common'
import { NestFactory } from '@nestjs/core'
import * as ElasticAPM from 'elastic-apm-node'

import { AppModule } from './app/app.module'
import { NestExpressApplication } from '@nestjs/platform-express'

export const SERVICE_NAME = process.env.TRACING_SERVICE_NAME || 'faucet-server'
export const SERVICE_VERSION = process.env.TRACING_SERVICE_VERSION || 'unknown'
export const ELASTIC_APM_ENDPOINT = process.env.ELASTIC_APM_ENDPOINT || ''
export const ELASTIC_APM_TOKEN = process.env.ELASTIC_APM_TOKEN || ''

export const apm = ElasticAPM.start({
serviceName: SERVICE_NAME,
secretToken: ELASTIC_APM_TOKEN,
serverUrl: ELASTIC_APM_ENDPOINT,
environment: 'local',
opentelemetryBridgeEnabled: true,
captureBody: 'all',
})

async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule)

Expand Down
93 changes: 39 additions & 54 deletions packages/frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { apm } from '@elastic/apm-rum'
import { ThemeProvider } from '@emotion/react'
import styled from '@emotion/styled'
import { Alert, Layout as AntdLayout } from 'antd'
import { useEffect, useMemo, useState } from 'react'
import { useEffect, useState } from 'react'

import { ErrorsContext } from './contexts/errors'
import Footer from './components/Footer'
Expand All @@ -12,7 +11,6 @@ import 'antd/dist/reset.css'
import useTheme from './hooks/useTheme'
import FaucetForm from './components/FaucetForm'
import Content from './components/Content'
import { TracingContext } from './contexts/tracing'
import { SubnetWithId } from './types'
import useRegisteredSubnets from './hooks/useRegisteredSubnets'
import { BigNumber, ethers } from 'ethers'
Expand Down Expand Up @@ -86,59 +84,46 @@ const App = () => {
[registeredSubnets]
)

const apmTransaction = useMemo(
() => apm.startTransaction('root', 'app', { managed: true }),
[]
)

return (
<ThemeProvider theme={theme}>
<TracingContext.Provider value={{ transaction: apmTransaction }}>
<ErrorsContext.Provider value={{ setErrors }}>
<SuccessesContext.Provider value={{ setSuccesses }}>
<SubnetsContext.Provider
value={{
loading: !Boolean(subnets),
data: subnets,
}}
>
<Layout>
<Header />
{Boolean(errors.length) && (
<Errors>
{errors.map((e) => (
<Alert
type="error"
showIcon
closable
message={e}
key={e}
/>
))}
</Errors>
)}
{Boolean(successes.length) && (
<Successes>
{successes.map((e) => (
<Alert
type="success"
showIcon
closable
message={e}
key={e}
/>
))}
</Successes>
)}
<Content>
<FaucetForm />
</Content>
<Footer />
</Layout>
</SubnetsContext.Provider>
</SuccessesContext.Provider>
</ErrorsContext.Provider>
</TracingContext.Provider>
<ErrorsContext.Provider value={{ setErrors }}>
<SuccessesContext.Provider value={{ setSuccesses }}>
<SubnetsContext.Provider
value={{
loading: !Boolean(subnets),
data: subnets,
}}
>
<Layout>
<Header />
{Boolean(errors.length) && (
<Errors>
{errors.map((e) => (
<Alert type="error" showIcon closable message={e} key={e} />
))}
</Errors>
)}
{Boolean(successes.length) && (
<Successes>
{successes.map((e) => (
<Alert
type="success"
showIcon
closable
message={e}
key={e}
/>
))}
</Successes>
)}
<Content>
<FaucetForm />
</Content>
<Footer />
</Layout>
</SubnetsContext.Provider>
</SuccessesContext.Provider>
</ErrorsContext.Provider>
</ThemeProvider>
)
}
Expand Down
Loading

0 comments on commit 477fde4

Please sign in to comment.