Skip to content

Commit 5e7640e

Browse files
authored
Merge pull request #491 from openedx/davidjoy/feat_new_relic_setcustomattribute
feat: set userId custom attribute for logging service
2 parents bc369e8 + 8e79342 commit 5e7640e

File tree

6 files changed

+63
-2
lines changed

6 files changed

+63
-2
lines changed

src/auth/AxiosJwtAuthService.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,13 @@ class AxiosJwtAuthService {
234234
administrator: decodedAccessToken.administrator,
235235
name: decodedAccessToken.name,
236236
});
237+
// Sets userId as a custom attribute that will be included with all subsequent log messages.
238+
// Very helpful for debugging.
239+
this.loggingService.setCustomAttribute('userId', decodedAccessToken.user_id);
237240
} else {
238241
this.setAuthenticatedUser(null);
242+
// Intentionally not setting `userId` in the logging service here because it would be useful
243+
// to know the previously logged in user for debugging refresh issues.
239244
}
240245

241246
return this.getAuthenticatedUser();

src/auth/AxiosJwtAuthService.test.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import AxiosJwtAuthService from './AxiosJwtAuthService';
77
const mockLoggingService = {
88
logInfo: jest.fn(),
99
logError: jest.fn(),
10+
setCustomAttribute: jest.fn(),
1011
};
1112

1213
const authOptions = {
@@ -204,6 +205,7 @@ beforeEach(() => {
204205
};
205206
mockLoggingService.logInfo.mockReset();
206207
mockLoggingService.logError.mockReset();
208+
mockLoggingService.setCustomAttribute.mockReset();
207209
service.getCsrfTokenService().clearCsrfTokenCache();
208210
axiosMock.onGet('/unauthorized').reply(401);
209211
axiosMock.onGet('/forbidden').reply(403);
@@ -861,6 +863,7 @@ describe('fetchAuthenticatedUser', () => {
861863
setJwtTokenRefreshResponseTo(200, jwtTokens.valid.encoded);
862864
return service.fetchAuthenticatedUser().then((authenticatedUserAccessToken) => {
863865
expect(authenticatedUserAccessToken).toEqual(jwtTokens.valid.formatted);
866+
expect(mockLoggingService.setCustomAttribute).toHaveBeenCalledWith('userId', jwtTokens.valid.formatted.userId);
864867
expectSingleCallToJwtTokenRefresh();
865868
});
866869
});
@@ -878,6 +881,7 @@ describe('fetchAuthenticatedUser', () => {
878881
setJwtTokenRefreshResponseTo(401, null);
879882
return service.fetchAuthenticatedUser({ forceRefresh: true }).then((authenticatedUserAccessToken) => {
880883
expect(authenticatedUserAccessToken).toEqual(null);
884+
expect(mockLoggingService.setCustomAttribute).not.toHaveBeenCalled();
881885
expectSingleCallToJwtTokenRefresh();
882886
});
883887
});

src/logging/MockLoggingService.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ class MockLoggingService {
1919
* @memberof MockLoggingService
2020
*/
2121
logError = jest.fn();
22+
23+
/**
24+
* Implemented as a jest.fn()
25+
*
26+
* @memberof MockLoggingService
27+
*/
28+
setCustomAttribute = jest.fn();
2229
}
2330

2431
export default MockLoggingService;

src/logging/NewRelicLoggingService.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ const pageActionNameIgnoredError = 'IGNORED_ERROR';
2323

2424
function sendPageAction(actionName, message, customAttributes) {
2525
if (process.env.NODE_ENV === 'development') {
26-
console.log(message, customAttributes); // eslint-disable-line
26+
console.log(actionName, message, customAttributes); // eslint-disable-line
2727
}
2828
if (window && typeof window.newrelic !== 'undefined') {
29+
// https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/addpageaction/
2930
window.newrelic.addPageAction(actionName, { message, ...customAttributes });
3031
}
3132
}
@@ -35,10 +36,21 @@ function sendError(error, customAttributes) {
3536
console.error(error, customAttributes); // eslint-disable-line
3637
}
3738
if (window && typeof window.newrelic !== 'undefined') {
39+
// https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/noticeerror/
3840
window.newrelic.noticeError(fixErrorLength(error), customAttributes);
3941
}
4042
}
4143

44+
function setCustomAttribute(name, value) {
45+
if (process.env.NODE_ENV === 'development') {
46+
console.log(name, value); // eslint-disable-line
47+
}
48+
if (window && typeof window.newrelic !== 'undefined') {
49+
// https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setcustomattribute/
50+
window.newrelic.setCustomAttribute(name, value);
51+
}
52+
}
53+
4254
/**
4355
* The NewRelicLoggingService is a concrete implementation of the logging service interface that
4456
* sends messages to NewRelic that can be seen in NewRelic Browser and NewRelic Insights. When in
@@ -65,7 +77,8 @@ function sendError(error, customAttributes) {
6577
* ```
6678
*
6779
* You can also add your own custom metrics as an additional argument, or see the code to find
68-
* other standard custom attributes.
80+
* other standard custom attributes. By default, userId is added (via setCustomAttribute) for logged
81+
* in users via the auth service (AuthAxiosJwtService).
6982
*
7083
* Requires the NewRelic Browser JavaScript snippet.
7184
*
@@ -155,4 +168,14 @@ export default class NewRelicLoggingService {
155168
sendError(errorStringOrObject, allCustomAttributes);
156169
}
157170
}
171+
172+
/**
173+
* Sets a custom attribute that will be included with all subsequent log messages.
174+
*
175+
* @param {string} name
176+
* @param {string|number|null} value
177+
*/
178+
setCustomAttribute(name, value) {
179+
setCustomAttribute(name, value);
180+
}
158181
}

src/logging/NewRelicLoggingService.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import NewRelicLoggingService, { MAX_ERROR_LENGTH } from './NewRelicLoggingServi
33
global.newrelic = {
44
addPageAction: jest.fn(),
55
noticeError: jest.fn(),
6+
setCustomAttribute: jest.fn(),
67
};
78

89
let service = null;
@@ -140,6 +141,17 @@ describe('NewRelicLoggingService', () => {
140141
});
141142
});
142143

144+
describe('setCustomAttribute', () => {
145+
beforeEach(() => {
146+
global.newrelic.setCustomAttribute.mockReset();
147+
});
148+
149+
it('calls New Relic client with name and value', () => {
150+
service.setCustomAttribute('foo', 'bar');
151+
expect(global.newrelic.setCustomAttribute).toHaveBeenCalledWith('foo', 'bar');
152+
});
153+
});
154+
143155
describe('ignoredErrors', () => {
144156
beforeEach(() => {
145157
global.newrelic.addPageAction.mockReset();

src/logging/interface.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ export function logError(errorStringOrObject, customAttributes) {
7171
return service.logError(errorStringOrObject, customAttributes);
7272
}
7373

74+
/**
75+
* Sets a custom attribute that will be included with all subsequent log messages.
76+
*
77+
* @param {string} name
78+
* @param {string|number|null} value
79+
*/
80+
export function setCustomAttribute(name, value) {
81+
return service.setCustomAttribute(name, value);
82+
}
83+
7484
/**
7585
*
7686
* @throws {Error} Thrown if the logging service has not yet been configured via {@link configure}.

0 commit comments

Comments
 (0)