Skip to content

Commit cea5fd9

Browse files
heyamstrask
andauthored
Hotfixes for Linux Consumption Plan (#1737)
* Fix verbose logging in Linux Consumption Plan when self diagnostic logging level is updated at runtime * Update java doc * Turn off Quick Pulse when the instrummentation key is null * Update RoleName for Linux Consumption Plan * Fix invalid instrumentation key * Reword * DefaultQuickPulsePingSender * Fix statsbeat is not sent in Linux Consumption Plan * RoleName is used in both header and payload * Turn off azure metadata service on functions and appsvc and AKS * Fix verbose logging * Fix null resourceProviderId for Linux Consumption Plan * Use scheduleWithFixedDelay * Fix a compilation error * Fix failing unit tests * Unify logging configuration a little more * Refactor Co-authored-by: Trask Stalnaker <[email protected]> Co-authored-by: Trask Stalnaker <[email protected]>
1 parent 0b3f9ce commit cea5fd9

File tree

12 files changed

+150
-71
lines changed

12 files changed

+150
-71
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Add metric filtering to telemetry processor [#1728](https://github.com/microsoft/ApplicationInsights-Java/pull/1728).
66
* Add log processor to telemetry processor [#1713](https://github.com/microsoft/ApplicationInsights-Java/pull/1713).
77
* Fix app id retrieval 404 for Linux Consumption Plan [#1730](https://github.com/microsoft/ApplicationInsights-Java/pull/1730).
8+
* Fix invalid instrumentation keys for Linux Consumption Plan [#1737](https://github.com/microsoft/ApplicationInsights-Java/pull/1737).
89

910
# Version 3.1.1-BETA.4
1011
* Reduce agent jar file size back to normal [#1716](https://github.com/microsoft/ApplicationInsights-Java/pull/1716).

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/LazyConfigurationAccessor.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import com.microsoft.applicationinsights.TelemetryConfiguration;
2828
import com.microsoft.applicationinsights.agent.internal.propagator.DelegatingPropagator;
2929
import com.microsoft.applicationinsights.agent.internal.sampling.DelegatingSampler;
30+
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.LoggingConfigurator;
31+
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.LoggingLevelConfigurator;
3032
import io.opentelemetry.instrumentation.api.aisdk.AiLazyConfiguration;
3133
import io.opentelemetry.instrumentation.api.config.Config;
3234
import org.slf4j.Logger;
@@ -100,15 +102,21 @@ static void setWebsiteSiteName(String websiteSiteName) {
100102
}
101103

102104
static void setSelfDiagnosticsLevel(String loggingLevel) {
103-
if (loggingLevel != null && !loggingLevel.isEmpty()) {
104-
LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
105-
List<ch.qos.logback.classic.Logger> loggerList = loggerContext.getLoggerList();
106-
logger.info("setting APPLICATIONINSIGHTS_SELF_DIAGNOSTICS_LEVEL to {}", loggingLevel);
107-
loggerList.stream().forEach(tmpLogger -> tmpLogger.setLevel(Level.toLevel(loggingLevel)));
108-
if (Level.toLevel(loggingLevel) == Level.DEBUG) {
109-
logger.debug("This should get logged after the logging level update.");
110-
}
105+
if (loggingLevel == null || !loggingLevel.isEmpty()) {
106+
return;
107+
}
108+
LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
109+
List<ch.qos.logback.classic.Logger> loggerList = loggerContext.getLoggerList();
110+
logger.info("setting APPLICATIONINSIGHTS_SELF_DIAGNOSTICS_LEVEL to {}", loggingLevel);
111+
LoggingLevelConfigurator configurator;
112+
try {
113+
configurator = new LoggingLevelConfigurator(loggingLevel);
114+
} catch (IllegalArgumentException exception) {
115+
logger.warn("unexpected self-diagnostic level: {}", loggingLevel);
116+
return;
111117
}
118+
loggerList.forEach(configurator::updateLoggerLevel);
119+
logger.debug("self-diagnostics logging level has been updated.");
112120
}
113121

114122
static boolean shouldSetConnectionString(boolean lazySetOptIn, String enableAgent) {

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/wasbootstrap/LoggingConfigurator.java

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,30 @@
2323
import org.slf4j.LoggerFactory;
2424

2525
import java.nio.file.Path;
26-
import java.util.Locale;
2726

2827
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
2928

30-
class LoggingConfigurator {
29+
public class LoggingConfigurator {
3130

3231
private final LoggerContext loggerContext;
3332

34-
private final Level level;
3533
private final String destination;
3634

3735
private final Path filePath;
3836
private final int fileMaxSizeMb;
3937
private final int fileMaxHistory;
4038

39+
private final LoggingLevelConfigurator loggingLevelConfigurator;
40+
4141
LoggingConfigurator(Configuration.SelfDiagnostics selfDiagnostics, Path agentPath) {
4242
loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
4343

44-
level = getLevel(selfDiagnostics.level);
4544
destination = selfDiagnostics.destination;
4645
filePath = agentPath.resolveSibling(selfDiagnostics.file.path);
4746
fileMaxSizeMb = selfDiagnostics.file.maxSizeMb;
4847
fileMaxHistory = selfDiagnostics.file.maxHistory;
48+
49+
loggingLevelConfigurator = new LoggingLevelConfigurator(selfDiagnostics.level);
4950
}
5051

5152
void configure() {
@@ -228,23 +229,11 @@ private Appender<ILoggingEvent> configureEtwAppender() {
228229
}
229230

230231
private void configureLoggingLevels() {
231-
// never want to log apache http at trace or debug, it's just way to verbose
232-
Level atLeastInfoLevel = getMaxLevel(level, Level.INFO);
233-
234-
Level otherLibsLevel = level == Level.INFO ? Level.WARN : level;
235-
236-
// TODO need something more reliable, currently will log too much WARN if "muzzleMatcher" logger name changes
237-
// muzzleMatcher logs at WARN level in order to make them visible, but really should only be enabled when debugging
238-
Level muzzleMatcherLevel = level.toInt() <= Level.DEBUG.toInt() ? level : getMaxLevel(level, Level.ERROR);
239-
240-
// never want to log apache http at trace or debug, it's just way to verbose
241-
loggerContext.getLogger("org.apache.http").setLevel(atLeastInfoLevel);
242-
// never want to log io.grpc.Context at trace or debug, as it logs confusing stack trace that looks like error but isn't
243-
loggerContext.getLogger("io.grpc.Context").setLevel(atLeastInfoLevel);
244-
// muzzleMatcher logs at WARN level, so by default this is OFF, but enabled when DEBUG logging is enabled
245-
loggerContext.getLogger("muzzleMatcher").setLevel(muzzleMatcherLevel);
246-
loggerContext.getLogger("com.microsoft.applicationinsights").setLevel(level);
247-
loggerContext.getLogger(ROOT_LOGGER_NAME).setLevel(otherLibsLevel);
232+
loggingLevelConfigurator.updateLoggerLevel(loggerContext.getLogger("org.apache.http"));
233+
loggingLevelConfigurator.updateLoggerLevel(loggerContext.getLogger("io.grpc.Context"));
234+
loggingLevelConfigurator.updateLoggerLevel(loggerContext.getLogger("muzzleMatcher"));
235+
loggingLevelConfigurator.updateLoggerLevel(loggerContext.getLogger("com.microsoft.applicationinsights"));
236+
loggingLevelConfigurator.updateLoggerLevel(loggerContext.getLogger(ROOT_LOGGER_NAME));
248237
}
249238

250239
private Encoder<ILoggingEvent> createEncoder() {
@@ -254,16 +243,4 @@ private Encoder<ILoggingEvent> createEncoder() {
254243
encoder.start();
255244
return encoder;
256245
}
257-
258-
private static Level getMaxLevel(Level level1, Level level2) {
259-
return level1.toInt() >= level2.toInt() ? level1 : level2;
260-
}
261-
262-
private static Level getLevel(String levelStr) {
263-
try {
264-
return Level.valueOf(levelStr.toUpperCase(Locale.ROOT));
265-
} catch (IllegalArgumentException e) {
266-
throw new IllegalStateException("Unexpected self-diagnostic level: " + levelStr);
267-
}
268-
}
269246
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.microsoft.applicationinsights.agent.internal.wasbootstrap;
2+
3+
import ch.qos.logback.classic.Level;
4+
import ch.qos.logback.classic.Logger;
5+
6+
import java.util.Locale;
7+
8+
public class LoggingLevelConfigurator {
9+
10+
private final Level level;
11+
12+
public LoggingLevelConfigurator(String levelStr) {
13+
try {
14+
this.level = Level.valueOf(levelStr.toUpperCase(Locale.ROOT));
15+
} catch (IllegalArgumentException e) {
16+
throw new IllegalStateException("Unexpected self-diagnostic level: " + levelStr);
17+
}
18+
}
19+
20+
public void updateLoggerLevel(Logger logger) {
21+
Level loggerLevel;
22+
String name = logger.getName();
23+
if (name.startsWith("org.apache.http")) {
24+
// never want to log apache http at trace or debug, it's just way too verbose
25+
loggerLevel = getAtLeastInfoLevel(level);
26+
} else if (name.startsWith("io.grpc.Context")) {
27+
// never want to log io.grpc.Context at trace or debug, as it logs confusing stack trace that looks like error but isn't
28+
loggerLevel = getAtLeastInfoLevel(level);
29+
} else if (name.startsWith("muzzleMatcher")) {
30+
// muzzleMatcher logs at WARN level, so by default this is OFF, but enabled when DEBUG logging is enabled
31+
loggerLevel = getMuzzleMatcherLevel(level);
32+
} else if (name.startsWith("com.microsoft.applicationinsights")) {
33+
loggerLevel = level;
34+
} else {
35+
loggerLevel = getOtherLibLevel(level);
36+
}
37+
logger.setLevel(loggerLevel);
38+
}
39+
40+
// never want to log apache http at trace or debug, it's just way to verbose
41+
private static Level getAtLeastInfoLevel(Level level) {
42+
return getMaxLevel(level, Level.INFO);
43+
}
44+
45+
private static Level getOtherLibLevel(Level level) {
46+
return level == Level.INFO ? Level.WARN : level;
47+
}
48+
49+
// TODO need something more reliable, currently will log too much WARN if "muzzleMatcher" logger name changes
50+
// muzzleMatcher logs at WARN level in order to make them visible, but really should only be enabled when debugging
51+
private static Level getMuzzleMatcherLevel(Level level) {
52+
return level.toInt() <= Level.DEBUG.toInt() ? level : getMaxLevel(level, Level.ERROR);
53+
}
54+
55+
private static Level getMaxLevel(Level level1, Level level2) {
56+
return level1.toInt() >= level2.toInt() ? level1 : level2;
57+
}
58+
}

core/src/main/java/com/microsoft/applicationinsights/TelemetryClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ public TelemetryClient() {
9595
* application session.
9696
*/
9797
public TelemetryContext getContext() {
98-
if (context == null || (context.getInstrumentationKey() != null && !context.getInstrumentationKey().equals(configuration.getInstrumentationKey()))) {
98+
if (context == null || (configuration.getInstrumentationKey() != null && !configuration.getInstrumentationKey().equals(context.getInstrumentationKey()))) {
9999
// lock and recheck there is still no initialized context. If so, create one.
100100
synchronized (TELEMETRY_CONTEXT_LOCK) {
101-
if (context==null || (context.getInstrumentationKey() != null && !context.getInstrumentationKey().equals(configuration.getInstrumentationKey()))) {
101+
if (context==null || (configuration.getInstrumentationKey() != null && !configuration.getInstrumentationKey().equals(context.getInstrumentationKey()))) {
102102
context = createInitializedContext();
103103
}
104104
}

core/src/main/java/com/microsoft/applicationinsights/internal/quickpulse/DefaultQuickPulsePingSender.java

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.concurrent.atomic.AtomicBoolean;
2727

2828
import com.google.common.annotations.VisibleForTesting;
29+
import com.google.common.base.Strings;
2930
import com.microsoft.applicationinsights.customExceptions.FriendlyException;
3031
import com.microsoft.applicationinsights.internal.channel.common.LazyHttpClient;
3132
import com.microsoft.applicationinsights.internal.util.LocalStringsUtils;
@@ -50,37 +51,23 @@ final class DefaultQuickPulsePingSender implements QuickPulsePingSender {
5051
private final TelemetryConfiguration configuration;
5152
private final HttpClient httpClient;
5253
private final QuickPulseNetworkHelper networkHelper = new QuickPulseNetworkHelper();
53-
private final String pingPrefix;
54-
private final String roleName;
54+
private volatile String pingPrefix; // cached for performance
5555
private final String instanceName;
5656
private final String machineName;
5757
private final String quickPulseId;
5858
private long lastValidTransmission = 0;
59+
private String roleName;
5960
private static final AtomicBoolean friendlyExceptionThrown = new AtomicBoolean();
6061

62+
// TODO (trask) roleName is ignored, clean this up after merging to AAD branch
6163
public DefaultQuickPulsePingSender(HttpClient httpClient, TelemetryConfiguration configuration, String machineName, String instanceName, String roleName, String quickPulseId) {
6264
this.configuration = configuration;
6365
this.httpClient = httpClient;
64-
this.roleName = roleName;
6566
this.instanceName = instanceName;
67+
this.roleName = roleName;
6668
this.machineName = machineName;
6769
this.quickPulseId = quickPulseId;
6870

69-
if (!LocalStringsUtils.isNullOrEmpty(roleName)) {
70-
roleName = "\"" + roleName + "\"";
71-
}
72-
73-
pingPrefix = "{" +
74-
"\"Documents\": null," +
75-
"\"Instance\":\"" + instanceName + "\"," +
76-
"\"InstrumentationKey\": null," +
77-
"\"InvariantVersion\": " + QuickPulse.QP_INVARIANT_VERSION + "," +
78-
"\"MachineName\":\"" + machineName + "\"," +
79-
"\"RoleName\":" + roleName + "," +
80-
"\"Metrics\": null," +
81-
"\"StreamId\": \"" + quickPulseId + "\"," +
82-
"\"Timestamp\": \"\\/Date(";
83-
8471
if (logger.isTraceEnabled()) {
8572
logger.trace("{} using endpoint {}", DefaultQuickPulsePingSender.class.getSimpleName(), getQuickPulseEndpoint());
8673
}
@@ -96,6 +83,12 @@ public DefaultQuickPulsePingSender(final HttpClient httpClient, final String mac
9683

9784
@Override
9885
public QuickPulseHeaderInfo ping(String redirectedEndpoint) {
86+
String instrumentationKey = getInstrumentationKey();
87+
if (Strings.isNullOrEmpty(instrumentationKey) && "java".equals(System.getenv("FUNCTIONS_WORKER_RUNTIME"))) {
88+
// Quick Pulse Ping uri will be null when the instrumentation key is null. When that happens, turn off quick pulse.
89+
return new QuickPulseHeaderInfo(QuickPulseStatus.QP_IS_OFF);
90+
}
91+
9992
final Date currentDate = new Date();
10093
final String endpointPrefix = LocalStringsUtils.isNullOrEmpty(redirectedEndpoint) ? getQuickPulseEndpoint() : redirectedEndpoint;
10194
final HttpPost request = networkHelper.buildPingRequest(currentDate, getQuickPulsePingUri(endpointPrefix), quickPulseId, machineName, roleName, instanceName);
@@ -133,6 +126,29 @@ public QuickPulseHeaderInfo ping(String redirectedEndpoint) {
133126
return onPingError(sendTime);
134127
}
135128

129+
// Linux Consumption Plan role name is lazily set
130+
private String getPingPrefix() {
131+
if (pingPrefix == null) {
132+
roleName = TelemetryConfiguration.getActive().getRoleName();
133+
134+
if (!LocalStringsUtils.isNullOrEmpty(roleName)) {
135+
roleName = "\"" + roleName + "\"";
136+
}
137+
138+
pingPrefix = "{" +
139+
"\"Documents\": null," +
140+
"\"Instance\":\"" + instanceName + "\"," +
141+
"\"InstrumentationKey\": null," +
142+
"\"InvariantVersion\": " + QuickPulse.QP_INVARIANT_VERSION + "," +
143+
"\"MachineName\":\"" + machineName + "\"," +
144+
"\"RoleName\":" + roleName + "," +
145+
"\"Metrics\": null," +
146+
"\"StreamId\": \"" + quickPulseId + "\"," +
147+
"\"Timestamp\": \"\\/Date(";
148+
}
149+
return pingPrefix;
150+
}
151+
136152
@VisibleForTesting
137153
String getQuickPulsePingUri(String endpointPrefix) {
138154
return endpointPrefix + "/ping?ikey=" + getInstrumentationKey();
@@ -142,6 +158,7 @@ private String getInstrumentationKey() {
142158
TelemetryConfiguration config = this.configuration == null ? TelemetryConfiguration.getActive() : configuration;
143159
return config.getInstrumentationKey();
144160
}
161+
145162
@VisibleForTesting
146163
String getQuickPulseEndpoint() {
147164
if (configuration != null) {
@@ -152,7 +169,7 @@ String getQuickPulseEndpoint() {
152169
}
153170

154171
private ByteArrayEntity buildPingEntity(long timeInMillis) {
155-
String sb = pingPrefix + timeInMillis +
172+
String sb = getPingPrefix() + timeInMillis +
156173
")\\/\"," +
157174
"\"Version\":\"2.2.0-738\"" +
158175
"}";

core/src/main/java/com/microsoft/applicationinsights/internal/statsbeat/AttachStatsbeat.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
package com.microsoft.applicationinsights.internal.statsbeat;
2323

24+
import com.google.common.base.Strings;
2425
import com.microsoft.applicationinsights.TelemetryClient;
2526
import com.microsoft.applicationinsights.telemetry.MetricTelemetry;
2627

@@ -44,6 +45,11 @@ class AttachStatsbeat extends BaseStatsbeat {
4445

4546
@Override
4647
protected void send() {
48+
// WEBSITE_HOSTNAME is lazily set in Linux Consumption Plan.
49+
if (Strings.isNullOrEmpty(resourceProviderId)) {
50+
resourceProviderId = initResourceProviderId(CustomDimensions.get().getResourceProvider(), null);
51+
}
52+
4753
MetricTelemetry statsbeatTelemetry = createStatsbeatTelemetry(ATTACH_METRIC_NAME, 0);
4854
statsbeatTelemetry.getProperties().put("rpId", resourceProviderId);
4955
telemetryClient.track(statsbeatTelemetry);
@@ -69,9 +75,10 @@ void updateMetadataInstance(MetadataInstanceResponse response) {
6975
static String initResourceProviderId(ResourceProvider resourceProvider, MetadataInstanceResponse response) {
7076
switch (resourceProvider) {
7177
case RP_APPSVC:
78+
// FIXME (heya) Need to test these env vars on App Services Linux & Windows
7279
return System.getenv(WEBSITE_SITE_NAME) + "/" + System.getenv(WEBSITE_HOME_STAMPNAME) + "/" + System.getenv(WEBSITE_HOSTNAME);
7380
case RP_FUNCTIONS:
74-
return System.getenv(WEBSITE_HOSTNAME);
81+
return System.getenv("WEBSITE_HOSTNAME");
7582
case RP_VM:
7683
if (response != null) {
7784
return response.getVmId() + "/" + response.getSubscriptionId();

core/src/main/java/com/microsoft/applicationinsights/internal/statsbeat/AzureMetadataService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ class AzureMetadataService implements Runnable {
3737
this.customDimensions = customDimensions;
3838
}
3939

40-
void scheduleAtFixedRate(long interval) {
40+
void scheduleWithFixedDelay(long interval) {
4141
// Querying Azure Metadata Service is required for every 15 mins since VM id will get updated frequently.
4242
// Starting and restarting a VM will generate a new VM id each time.
4343
// TODO (heya) need to confirm if restarting VM will also restart the Java Agent
44-
scheduledExecutor.scheduleAtFixedRate(this, interval, interval, TimeUnit.SECONDS);
44+
scheduledExecutor.scheduleWithFixedDelay(this, interval, interval, TimeUnit.SECONDS);
4545
}
4646

4747
// visible for testing

core/src/main/java/com/microsoft/applicationinsights/internal/statsbeat/BaseStatsbeat.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public void run() {
6767
try {
6868
// For Linux Consumption Plan, connection string is lazily set.
6969
// There is no need to send statsbeat when cikey is empty.
70-
if (Strings.isNullOrEmpty(CustomDimensions.get().getCustomerIkey())) {
70+
if (Strings.isNullOrEmpty(TelemetryConfiguration.getActive().getInstrumentationKey())) {
7171
return;
7272
}
7373
send();

core/src/main/java/com/microsoft/applicationinsights/internal/statsbeat/CustomDimensions.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
package com.microsoft.applicationinsights.internal.statsbeat;
2323

24+
import com.google.common.base.Strings;
2425
import com.microsoft.applicationinsights.TelemetryConfiguration;
2526
import com.microsoft.applicationinsights.internal.system.SystemInformation;
2627
import com.microsoft.applicationinsights.internal.util.PropertyHelper;
@@ -35,10 +36,10 @@ class CustomDimensions {
3536
private volatile OperatingSystem operatingSystem;
3637

3738
private final String attachType;
38-
private final String customerIkey;
3939
private final String runtimeVersion;
4040
private final String language;
4141
private final String sdkVersion;
42+
private String customerIkey;
4243

4344
static CustomDimensions get() {
4445
return instance;
@@ -101,6 +102,10 @@ void populateProperties(Map<String, String> properties) {
101102
properties.put("rp", resourceProvider.getValue());
102103
properties.put("os", operatingSystem.getValue());
103104
properties.put("attach", attachType);
105+
// For Linux Consumption Plan, customer iKey needs to be updated here since it's lazily set.
106+
if (Strings.isNullOrEmpty(customerIkey)) {
107+
customerIkey = TelemetryConfiguration.getActive().getInstrumentationKey();
108+
}
104109
properties.put("cikey", customerIkey);
105110
properties.put("runtimeVersion", runtimeVersion);
106111
properties.put("language", language);

0 commit comments

Comments
 (0)