Skip to content

Commit d0760de

Browse files
authored
[Identity] ChainedTokenCredential logging fix (#14847)
Fixes to the ChainedTokenCredential logging. It now documents what credential succeeds. Also added a test. Fixes #6769
1 parent bb0ffe0 commit d0760de

File tree

4 files changed

+81
-2
lines changed

4 files changed

+81
-2
lines changed

sdk/identity/identity/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 2.0.0-beta.3 (Unreleased)
44

5+
- Fixed issue with the logging of success messages on the `DefaultAzureCredential` and the `ChainedTokenCredential`. These messages will now mention the internal credential that succeeded.
56
- The feature of persistence caching of credentials (introduced in 2.0.0-beta.1) is now supported on Node.js 15 as well.
67
- `AuthenticationRequiredError` (introduced in 2.0.0-beta.1) now has the same impact on `ChainedTokenCredential` as the `CredentialUnavailableError` which is to allow the next credential in the chain to be tried.
78

sdk/identity/identity/src/credentials/chainedTokenCredential.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { createSpan } from "../util/tracing";
77
import { SpanStatusCode } from "@azure/core-tracing";
88
import { credentialLogger, formatSuccess, formatError } from "../util/logging";
99

10-
const logger = credentialLogger("ChainedTokenCredential");
10+
/**
11+
* @internal
12+
*/
13+
export const logger = credentialLogger("ChainedTokenCredential");
1114

1215
/**
1316
* Enables multiple `TokenCredential` implementations to be tried in order
@@ -53,13 +56,15 @@ export class ChainedTokenCredential implements TokenCredential {
5356
*/
5457
async getToken(scopes: string | string[], options?: GetTokenOptions): Promise<AccessToken> {
5558
let token = null;
59+
let successfulCredentialName = "";
5660
const errors = [];
5761

5862
const { span, updatedOptions } = createSpan("ChainedTokenCredential-getToken", options);
5963

6064
for (let i = 0; i < this._sources.length && token === null; i++) {
6165
try {
6266
token = await this._sources[i].getToken(scopes, updatedOptions);
67+
successfulCredentialName = this._sources[i].constructor.name;
6368
} catch (err) {
6469
if (
6570
err.name === "CredentialUnavailableError" ||
@@ -85,7 +90,7 @@ export class ChainedTokenCredential implements TokenCredential {
8590

8691
span.end();
8792

88-
logger.getToken.info(formatSuccess(scopes));
93+
logger.getToken.info(`Result for ${successfulCredentialName}: ${formatSuccess(scopes)}`);
8994

9095
if (token === null) {
9196
throw new CredentialUnavailableError("Failed to retrieve a valid token");

sdk/identity/identity/src/util/logging.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export function credentialLoggerInstance(
109109
* It has all the properties of a CredentialLoggerInstance, plus other logger instances, one per method.
110110
*/
111111
export interface CredentialLogger extends CredentialLoggerInstance {
112+
parent: AzureLogger;
112113
getToken: CredentialLoggerInstance;
113114
}
114115

@@ -126,6 +127,7 @@ export function credentialLogger(title: string, log: AzureLogger = logger): Cred
126127
const credLogger = credentialLoggerInstance(title, undefined, log);
127128
return {
128129
...credLogger,
130+
parent: log,
129131
getToken: credentialLoggerInstance("=> getToken()", credLogger, log)
130132
};
131133
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
import assert from "assert";
5+
import { ChainedTokenCredential, TokenCredential, AccessToken } from "../../../src";
6+
import Sinon from "sinon";
7+
import { logger as chainedTokenCredentialLogger } from "../../../src/credentials/chainedTokenCredential";
8+
9+
class TestMockCredential implements TokenCredential {
10+
constructor(public returnPromise: Promise<AccessToken | null>) {}
11+
12+
getToken(): Promise<AccessToken | null> {
13+
return this.returnPromise;
14+
}
15+
}
16+
17+
describe("ChainedTokenCredential", function() {
18+
it("Logs the expected successful message", async () => {
19+
const chainedTokenCredential = new ChainedTokenCredential(
20+
new TestMockCredential(Promise.resolve({ token: "firstToken", expiresOnTimestamp: 0 }))
21+
);
22+
23+
const infoSpy = Sinon.spy(chainedTokenCredentialLogger.parent, "info");
24+
const getTokenInfoSpy = Sinon.spy(chainedTokenCredentialLogger.getToken, "info");
25+
26+
const accessToken = await chainedTokenCredential.getToken("<scope>");
27+
assert.notStrictEqual(accessToken, null);
28+
29+
assert.equal(
30+
infoSpy.getCalls()[0].args.join(" "),
31+
"ChainedTokenCredential => getToken() => Result for TestMockCredential: SUCCESS. Scopes: <scope>."
32+
);
33+
assert.equal(
34+
getTokenInfoSpy.getCalls()[0].args[0],
35+
"Result for TestMockCredential: SUCCESS. Scopes: <scope>."
36+
);
37+
38+
infoSpy.restore();
39+
getTokenInfoSpy.restore();
40+
});
41+
42+
it("Doesn't throw with a clossure credential", async () => {
43+
function mockCredential(returnPromise: Promise<AccessToken | null>): TokenCredential {
44+
return {
45+
getToken: () => returnPromise
46+
};
47+
}
48+
49+
const chainedTokenCredential = new ChainedTokenCredential(
50+
mockCredential(Promise.resolve({ token: "firstToken", expiresOnTimestamp: 0 }))
51+
);
52+
53+
const infoSpy = Sinon.spy(chainedTokenCredentialLogger.parent, "info");
54+
const getTokenInfoSpy = Sinon.spy(chainedTokenCredentialLogger.getToken, "info");
55+
56+
const accessToken = await chainedTokenCredential.getToken("<scope>");
57+
assert.notStrictEqual(accessToken, null);
58+
59+
assert.equal(
60+
infoSpy.getCalls()[0].args.join(" "),
61+
"ChainedTokenCredential => getToken() => Result for Object: SUCCESS. Scopes: <scope>."
62+
);
63+
assert.equal(
64+
getTokenInfoSpy.getCalls()[0].args[0],
65+
"Result for Object: SUCCESS. Scopes: <scope>."
66+
);
67+
68+
infoSpy.restore();
69+
getTokenInfoSpy.restore();
70+
});
71+
});

0 commit comments

Comments
 (0)