Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Trace api calls #32779

Merged
merged 37 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ab493b5
feat: Trace api calls
rodrigok Jul 12, 2024
558575e
Start using open telemetry
rodrigok Jul 18, 2024
e7f8be7
Init tracing support for microservices
rodrigok Oct 15, 2024
16188aa
Fix database tracing
rodrigok Oct 15, 2024
8894c74
chore: new api for tracing
sampaiodiego Oct 15, 2024
905ec72
chore: use new tracing api
sampaiodiego Oct 15, 2024
c92154c
fix typecheck
sampaiodiego Oct 15, 2024
518c75d
chore: MaybePromise
sampaiodiego Oct 15, 2024
ebfcc4e
chore: cleanup
rodrigok Oct 16, 2024
8be8fda
chore: remove MaybePromise
sampaiodiego Oct 16, 2024
1549d7b
add isTracingEnabled
sampaiodiego Oct 16, 2024
24a547a
use more tracerSpan
sampaiodiego Oct 16, 2024
5acbbf7
chore: add context propagation
ricardogarim Oct 16, 2024
b373cfc
fix yarn.lock
sampaiodiego Oct 16, 2024
d11a285
fix tracer return value
sampaiodiego Oct 16, 2024
3059d94
start tracing on micro services
sampaiodiego Oct 16, 2024
d461430
code cleanup
sampaiodiego Oct 17, 2024
148b09a
check tracing enabled on initDatabaseTracing
sampaiodiego Oct 17, 2024
4160791
remove unneeded dependencies
sampaiodiego Oct 17, 2024
8852710
pass with not tests
sampaiodiego Oct 17, 2024
336058b
add tracing to missing services
sampaiodiego Oct 17, 2024
8755288
add dependencies to meteor
sampaiodiego Oct 17, 2024
794c7cf
fix startup
sampaiodiego Oct 17, 2024
5185b28
Merge remote-tracking branch 'origin/develop' into meteor3/tracing
sampaiodiego Oct 17, 2024
9643b12
handle errors
sampaiodiego Oct 17, 2024
4ba4f43
Merge remote-tracking branch 'origin/develop' into meteor3/tracing
sampaiodiego Oct 17, 2024
303583e
Merge remote-tracking branch 'origin/develop' into meteor3/tracing
sampaiodiego Oct 18, 2024
d2d0c05
fix arguments stringify
sampaiodiego Oct 18, 2024
eb50b66
Merge remote-tracking branch 'origin/develop' into meteor3/tracing
sampaiodiego Oct 18, 2024
3d15398
enable tracing only via env var TRACING_ENABLED
sampaiodiego Oct 18, 2024
b2b24c7
remove moleculer tracing options. we are using our own
sampaiodiego Oct 18, 2024
f1ba646
Merge branch 'develop' into meteor3/tracing
sampaiodiego Oct 18, 2024
f61d767
throw error if startTracing no called
sampaiodiego Oct 18, 2024
1e2e11e
add statusCode for APIs
sampaiodiego Oct 18, 2024
1339608
end span on commandFailed
sampaiodiego Oct 18, 2024
67631d6
Merge branch 'develop' into meteor3/tracing
kodiakhq[bot] Oct 18, 2024
c0d4135
Merge remote-tracking branch 'origin/develop' into meteor3/tracing
ggazzo Oct 19, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ data/
registration.yaml

storybook-static
development/tempo-data/
26 changes: 24 additions & 2 deletions apps/meteor/app/api/server/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Logger } from '@rocket.chat/logger';
import { Users } from '@rocket.chat/models';
import { Random } from '@rocket.chat/random';
import type { JoinPathPattern, Method } from '@rocket.chat/rest-typings';
import { tracerSpan } from '@rocket.chat/tracing';
import { Accounts } from 'meteor/accounts-base';
import { DDP } from 'meteor/ddp';
import { DDPCommon } from 'meteor/ddp-common';
Expand Down Expand Up @@ -645,8 +646,29 @@ export class APIClass<TBasePath extends string = ''> extends Restivus {
this.queryFields = options.queryFields;
this.parseJsonQuery = api.parseJsonQuery.bind(this as PartialThis);

result =
(await DDP._CurrentInvocation.withValue(invocation as any, async () => originalAction.apply(this))) || API.v1.success();
result = await tracerSpan(
`${this.request.method} ${this.request.url}`,
{
attributes: {
url: this.request.url,
route: this.request.route,
method: this.request.method,
userId: this.userId,
},
},
async (span) => {
if (span) {
this.response.setHeader('X-Trace-Id', span.spanContext().traceId);
}

const result =
(await DDP._CurrentInvocation.withValue(invocation as any, async () => originalAction.apply(this))) || API.v1.success();

span?.setAttribute('status', result.statusCode);

return result;
},
);

log.http({
status: result.statusCode,
Expand Down
18 changes: 15 additions & 3 deletions apps/meteor/app/lib/server/lib/debug.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { InstanceStatus } from '@rocket.chat/instance-status';
import { Logger } from '@rocket.chat/logger';
import { tracerActiveSpan } from '@rocket.chat/tracing';
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
import _ from 'underscore';
Expand Down Expand Up @@ -72,9 +73,20 @@ const wrapMethods = function (name, originalHandler, methodsMap) {
...getMethodArgs(name, originalArgs),
});

const result = originalHandler.apply(this, originalArgs);
end();
return result;
return tracerActiveSpan(
`Method ${name}`,
{
attributes: {
method: name,
userId: this.userId,
},
},
async () => {
const result = await originalHandler.apply(this, originalArgs);
end();
return result;
},
);
};
};

Expand Down
16 changes: 15 additions & 1 deletion apps/meteor/app/metrics/server/lib/collectMetrics.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import http from 'http';

import { Statistics } from '@rocket.chat/models';
import { tracerSpan } from '@rocket.chat/tracing';
import connect from 'connect';
import { Facts } from 'meteor/facts-base';
import { Meteor } from 'meteor/meteor';
Expand Down Expand Up @@ -169,7 +170,20 @@ const updatePrometheusConfig = async (): Promise<void> => {
host: process.env.BIND_IP || '0.0.0.0',
});

timer = setInterval(setPrometheusData, 5000);
timer = setInterval(async () => {
void tracerSpan(
'setPrometheusData',
{
attributes: {
port: is.port,
host: process.env.BIND_IP || '0.0.0.0',
},
},
() => {
void setPrometheusData();
},
);
}, 5000);
}

clearInterval(resetTimer);
Expand Down
26 changes: 20 additions & 6 deletions apps/meteor/app/notification-queue/server/NotificationQueue.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { INotification, INotificationItemPush, INotificationItemEmail, NotificationItem, IUser } from '@rocket.chat/core-typings';
import { NotificationQueue, Users } from '@rocket.chat/models';
import { tracerSpan } from '@rocket.chat/tracing';
import { Meteor } from 'meteor/meteor';

import { SystemLogger } from '../../../server/lib/logger/system';
Expand Down Expand Up @@ -43,25 +44,37 @@ class NotificationClass {

setTimeout(async () => {
try {
await this.worker();
const continueLater = await tracerSpan(
'NotificationWorker',
{
attributes: {
workerTime: new Date().toISOString(),
},
},
() => this.worker(),
);

if (continueLater) {
this.executeWorkerLater();
}
} catch (err) {
SystemLogger.error({ msg: 'Error sending notification', err });
this.executeWorkerLater();
}
}, this.cyclePause);
}

async worker(counter = 0): Promise<void> {
async worker(counter = 0): Promise<boolean> {
const notification = await this.getNextNotification();

if (!notification) {
return this.executeWorkerLater();
return true;
}

// Once we start notifying the user we anticipate all the schedules
const flush = await NotificationQueue.clearScheduleByUserId(notification.uid);

// start worker again it queue flushed
// start worker again if queue flushed
if (flush.modifiedCount) {
await NotificationQueue.unsetSendingById(notification._id);
return this.worker(counter);
Expand All @@ -86,9 +99,10 @@ class NotificationClass {
}

if (counter >= this.maxBatchSize) {
return this.executeWorkerLater();
return true;
}
await this.worker(counter++);

return this.worker(counter++);
}

getNextNotification(): Promise<INotification | null> {
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@
"@nivo/heatmap": "0.84.0",
"@nivo/line": "0.84.0",
"@nivo/pie": "0.84.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/exporter-trace-otlp-grpc": "^0.53.0",
"@opentelemetry/sdk-node": "^0.53.0",
"@react-aria/color": "^3.0.0-beta.15",
"@react-aria/toolbar": "^3.0.0-beta.1",
"@react-pdf/renderer": "^3.4.5",
Expand Down Expand Up @@ -278,6 +281,7 @@
"@rocket.chat/sha256": "workspace:^",
"@rocket.chat/string-helpers": "~0.31.25",
"@rocket.chat/tools": "workspace:^",
"@rocket.chat/tracing": "workspace:^",
"@rocket.chat/ui-avatar": "workspace:^",
"@rocket.chat/ui-client": "workspace:^",
"@rocket.chat/ui-composer": "workspace:^",
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/packages/rocketchat-mongo-config/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const mongoConnectionOptions = {
// add retryWrites=false if not present in MONGO_URL
...(!process.env.MONGO_URL.includes('retryWrites') && { retryWrites: false }),
// ignoreUndefined: false, // TODO evaluate adding this config

// TODO ideally we should call isTracingEnabled(), but since this is a Meteor package we can't :/
monitorCommands: ['yes', 'true'].includes(String(process.env.TRACING_ENABLED).toLowerCase()),
};

const mongoOptionStr = process.env.MONGO_OPTIONS;
Expand Down
49 changes: 26 additions & 23 deletions apps/meteor/server/cron/statistics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,39 @@ import { cronJobs } from '@rocket.chat/cron';
import type { Logger } from '@rocket.chat/logger';
import { Statistics } from '@rocket.chat/models';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { tracerSpan } from '@rocket.chat/tracing';
import { Meteor } from 'meteor/meteor';

import { getWorkspaceAccessToken } from '../../app/cloud/server';
import { statistics } from '../../app/statistics/server';

async function generateStatistics(logger: Logger): Promise<void> {
const cronStatistics = await statistics.save();

try {
const token = await getWorkspaceAccessToken();
const headers = { ...(token && { Authorization: `Bearer ${token}` }) };

const response = await fetch('https://collector.rocket.chat/', {
method: 'POST',
body: {
...cronStatistics,
host: Meteor.absoluteUrl(),
},
headers,
});

const { statsToken } = await response.json();

if (statsToken != null) {
await Statistics.updateOne({ _id: cronStatistics._id }, { $set: { statsToken } });
await tracerSpan('generateStatistics', {}, async () => {
const cronStatistics = await statistics.save();

try {
const token = await getWorkspaceAccessToken();
const headers = { ...(token && { Authorization: `Bearer ${token}` }) };

const response = await fetch('https://collector.rocket.chat/', {
method: 'POST',
body: {
...cronStatistics,
host: Meteor.absoluteUrl(),
},
headers,
});

const { statsToken } = await response.json();

if (statsToken != null) {
await Statistics.updateOne({ _id: cronStatistics._id }, { $set: { statsToken } });
}
} catch (error) {
/* error*/
logger.warn('Failed to send usage report');
}
} catch (error) {
/* error*/
logger.warn('Failed to send usage report');
}
});
}

export async function statsCron(logger: Logger): Promise<void> {
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/server/database/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { initDatabaseTracing } from '@rocket.chat/tracing';
import { MongoInternals } from 'meteor/mongo';

export const { db, client } = MongoInternals.defaultRemoteCollectionDriver().mongo;

initDatabaseTracing(client);
1 change: 1 addition & 0 deletions apps/meteor/server/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './tracing';
import './models/startup';
/**
* ./settings uses top level await, in theory the settings creation
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/server/models/raw/BaseRaw.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { traceInstanceMethods } from '@rocket.chat/core-services';
import type { RocketChatRecordDeleted } from '@rocket.chat/core-typings';
import type { IBaseModel, DefaultFields, ResultFields, FindPaginated, InsertionModel } from '@rocket.chat/model-typings';
import type { Updater } from '@rocket.chat/models';
Expand Down Expand Up @@ -76,6 +77,8 @@ export abstract class BaseRaw<
void this.createIndexes();

this.preventSetUpdatedAt = options?.preventSetUpdatedAt ?? false;

return traceInstanceMethods(this);
}

private pendingIndexes: Promise<void> | undefined;
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/server/tracing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { startTracing } from '@rocket.chat/tracing';

startTracing({ service: 'core' });
16 changes: 16 additions & 0 deletions development/agent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
server:
log_level: debug

traces:
configs:
- name: default
receivers:
otlp:
protocols:
grpc:
remote_write:
- endpoint: tempo:4317
insecure: true
batch:
timeout: 5s
send_batch_size: 100
24 changes: 24 additions & 0 deletions development/collector.config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
receivers:
otlp:
protocols:
grpc:
http:

processors:
batch:
timeout: 100ms

exporters:
logging:
loglevel: debug
otlp/1:
endpoint: tempo:4317
tls:
insecure: true

service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/1]
Loading
Loading