Skip to content

Commit

Permalink
feat: db.client.response.returned_rows (#276)
Browse files Browse the repository at this point in the history
non-fork version of #271

- [x] tests
- [x] changelog entry

---

thanks @soccermax
  • Loading branch information
sjvans authored Jan 9, 2025
1 parent 728f05d commit 67933be
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 3 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
The format is based on [Keep a Changelog](http://keepachangelog.com/).

## Version 1.2.0 - tbd

### Added

- Trace attribute `db.client.response.returned_rows` for queries via `cds.ql`

### Changed

### Fixed

### Removed

## Version 1.1.2 - 2024-12-10

### Fixed
Expand Down
21 changes: 19 additions & 2 deletions lib/tracing/trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ function _getParentSpan() {
// root span gets request attributes
_setAttributes(parent, _getRequestAttributes())
if (HRTIME) parent.startTime = cds.context.http?.req?.__hrnow || _hrnow()
if (ADJUST_ROOT_NAME && parent.attributes[ATTR_URL_PATH])
parent.name += ' ' + parent.attributes[ATTR_URL_PATH]
if (ADJUST_ROOT_NAME && parent.attributes[ATTR_URL_PATH]) parent.name += ' ' + parent.attributes[ATTR_URL_PATH]
}
if (!parent?._is_async) cds.context._otelctx.setValue(cds.context._otelKey, parent)
}
Expand Down Expand Up @@ -185,6 +184,23 @@ function _setAttributes(span, attributes) {
attributes.forEach((value, key) => span.setAttribute(key, value))
}

const _addDbRowCount = (span, res) => {
if (!span.attributes['db.statement']) return
if (!['all', 'run'].includes(span.attributes['code.function'])) return

let rowCount
switch (span.attributes['db.operation']) {
case 'DELETE':
case 'UPDATE':
case 'CREATE':
rowCount = res.changes
break
case 'READ':
rowCount = res.length ?? 1
}
if (rowCount != null) span.setAttribute('db.client.response.returned_rows', rowCount)
}

function trace(name, fn, targetObj, args, options = {}) {
// REVISIT: only start tracing once served
if (!cds._telemetry.tracer._active) return fn.apply(targetObj, args)
Expand Down Expand Up @@ -292,6 +308,7 @@ function trace(name, fn, targetObj, args, options = {}) {
*/
return otel.context.with(cds.context?._otelctx.setValue(cds.context._otelKey, span), () => {
const onSuccess = res => {
_addDbRowCount(span, res)
span.setStatus({ code: SpanStatusCode.OK })
return res
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cap-js/telemetry",
"version": "1.1.2",
"version": "1.2.0",
"description": "CDS plugin providing observability features, incl. automatic OpenTelemetry instrumentation.",
"repository": {
"type": "git",
Expand Down
38 changes: 38 additions & 0 deletions test/tracing-attributes.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
process.env.cds_requires_telemetry_tracing_exporter_module = '@opentelemetry/sdk-trace-node'

const cds = require('@sap/cds')
const { expect, data } = cds.test().in(__dirname + '/bookshop')

describe('tracing attributes', () => {
beforeEach(data.reset)

const log = jest.spyOn(console, 'dir')
beforeEach(log.mockClear)

describe('db.client.response.returned_rows', () => {
test('SELECT', async () => {
await SELECT.from('sap.capire.bookshop.Books')
const output = JSON.stringify(log.mock.calls)
expect(output).to.match(/db\.client\.response.returned_rows":5/)
})

test('INSERT', async () => {
await INSERT.into('sap.capire.bookshop.Books').entries([{ ID: 1 }, { ID: 2 }])
const output = JSON.stringify(log.mock.calls)
expect(output).to.match(/db\.client\.response.returned_rows":2/)
})

test('UPDATE', async () => {
await UPDATE('sap.capire.bookshop.Books').set({ stock: 42 }).where('ID > 250')
const output = JSON.stringify(log.mock.calls)
expect(output).to.match(/db\.client\.response.returned_rows":3/)
})

test('DELETE', async () => {
await DELETE.from('sap.capire.bookshop.Books')
const output = JSON.stringify(log.mock.calls)
expect(output).to.match(/db\.client\.response.returned_rows":0/) //> texts
expect(output).to.match(/db\.client\.response.returned_rows":5/)
})
})
})

0 comments on commit 67933be

Please sign in to comment.