Skip to content

Commit 694e21d

Browse files
authored
Better Azure Functions consumption plan behavior (#2397)
* Better Azure Functions consumption plan behavior * Spotless * Fix * Fixes * Lazy endpoints * simplify * Logging * Update log location
1 parent 18be244 commit 694e21d

File tree

20 files changed

+354
-204
lines changed

20 files changed

+354
-204
lines changed
Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,31 @@
2121

2222
package com.microsoft.applicationinsights.agent.bootstrap;
2323

24-
public class AiLazyConfiguration {
24+
import java.util.function.Supplier;
25+
import javax.annotation.Nullable;
2526

26-
private static volatile Accessor accessor;
27+
public class AzureFunctions {
2728

28-
public static void setAccessor(Accessor accessor) {
29-
AiLazyConfiguration.accessor = accessor;
29+
private static volatile Supplier<Boolean> hasConnectionString;
30+
@Nullable private static volatile Runnable configure;
31+
32+
public static void setup(Supplier<Boolean> hasConnectionString, Runnable initializer) {
33+
AzureFunctions.hasConnectionString = hasConnectionString;
34+
AzureFunctions.configure = initializer;
3035
}
3136

32-
public static void lazyLoad() {
33-
if (accessor != null) {
34-
accessor.lazyLoad();
35-
}
37+
public static boolean hasConnectionString() {
38+
return hasConnectionString.get();
3639
}
3740

38-
public interface Accessor {
39-
void lazyLoad();
41+
public static void configureOnce() {
42+
if (configure != null) {
43+
if (!hasConnectionString()) {
44+
configure.run();
45+
}
46+
configure = null;
47+
}
4048
}
4149

42-
private AiLazyConfiguration() {}
50+
private AzureFunctions() {}
4351
}

agent/agent-bootstrap/src/main/java/com/microsoft/applicationinsights/agent/bootstrap/diagnostics/log/ApplicationInsightsCsvLayout.java

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@
2323

2424
import ch.qos.logback.classic.PatternLayout;
2525
import ch.qos.logback.classic.spi.ILoggingEvent;
26+
import ch.qos.logback.classic.spi.IThrowableProxy;
27+
import ch.qos.logback.classic.spi.ThrowableProxy;
2628
import com.microsoft.applicationinsights.agent.bootstrap.diagnostics.ApplicationMetadataFactory;
2729
import com.microsoft.applicationinsights.agent.bootstrap.diagnostics.DiagnosticsHelper;
30+
import java.io.PrintWriter;
31+
import java.io.StringWriter;
2832

2933
public class ApplicationInsightsCsvLayout extends PatternLayout {
3034

3135
private static final String PREFIX = "LanguageWorkerConsoleLogMS_APPLICATION_INSIGHTS_LOGS";
3236

37+
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
38+
3339
private static final ApplicationMetadataFactory applicationMetadataFactory =
3440
DiagnosticsHelper.getMetadataFactory();
3541
private final String qualifiedSdkVersion;
@@ -40,28 +46,43 @@ public ApplicationInsightsCsvLayout(String qualifiedSdkVersion) {
4046

4147
@Override
4248
public String doLayout(ILoggingEvent event) {
43-
StringBuilder stringBuilder = new StringBuilder();
44-
stringBuilder.append(PREFIX);
45-
stringBuilder.append(" ");
46-
stringBuilder.append(event.getTimeStamp());
47-
stringBuilder.append(",");
48-
stringBuilder.append(event.getLevel().toString());
49-
stringBuilder.append(",");
50-
stringBuilder.append(event.getLoggerName());
51-
stringBuilder.append(",");
52-
stringBuilder.append("\"");
53-
stringBuilder.append(event.getFormattedMessage());
54-
stringBuilder.append("\"");
55-
stringBuilder.append(",");
56-
stringBuilder.append(applicationMetadataFactory.getSiteName().getValue());
57-
stringBuilder.append(",");
58-
stringBuilder.append(applicationMetadataFactory.getInstrumentationKey().getValue());
59-
stringBuilder.append(",");
60-
stringBuilder.append(qualifiedSdkVersion);
61-
stringBuilder.append(",");
62-
stringBuilder.append("java");
63-
stringBuilder.append(System.getProperty("line.separator"));
49+
String message = event.getFormattedMessage();
50+
IThrowableProxy throwableProxy = event.getThrowableProxy();
51+
Throwable throwable = null;
52+
if (throwableProxy instanceof ThrowableProxy) {
53+
// there is only one other subclass of ch.qos.logback.classic.spi.IThrowableProxy
54+
// and it is only used for logging exceptions over the wire
55+
throwable = ((ThrowableProxy) throwableProxy).getThrowable();
56+
}
57+
if (throwable != null) {
58+
message += " ";
59+
StringWriter sw = new StringWriter();
60+
throwable.printStackTrace(new PrintWriter(sw, true));
61+
message += sw;
62+
}
63+
return PREFIX
64+
+ " "
65+
+ event.getTimeStamp()
66+
+ ","
67+
+ event.getLevel().toString()
68+
+ ","
69+
+ event.getLoggerName()
70+
+ ","
71+
+ "\""
72+
+ formatForCsv(message)
73+
+ "\""
74+
+ ","
75+
+ applicationMetadataFactory.getSiteName().getValue()
76+
+ ","
77+
+ applicationMetadataFactory.getInstrumentationKey().getValue()
78+
+ ","
79+
+ qualifiedSdkVersion
80+
+ ","
81+
+ "java"
82+
+ System.getProperty("line.separator");
83+
}
6484

65-
return stringBuilder.toString();
85+
private static String formatForCsv(String str) {
86+
return str.replace(LINE_SEPARATOR, " ").replace('\"', '\'');
6687
}
6788
}

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -539,11 +539,7 @@ private static String getDefaultPath() {
539539
if (DiagnosticsHelper.useAppSvcRpIntegrationLogging()) {
540540
return StatusFile.getLogDir() + "/" + DEFAULT_NAME;
541541
}
542-
if (DiagnosticsHelper.useFunctionsRpIntegrationLogging()
543-
&& !DiagnosticsHelper.isOsWindows()) {
544-
return "/var/log/applicationinsights/" + DEFAULT_NAME;
545-
}
546-
// azure spring cloud
542+
// azure functions and azure spring cloud
547543
return DEFAULT_NAME;
548544
}
549545
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,8 +592,7 @@ private static String getConfigPath() {
592592

593593
private static String getWebsiteSiteNameEnvVar() {
594594
String value = getEnvVar(WEBSITE_SITE_NAME);
595-
// TODO what is the best way to identify running as Azure Functions worker?
596-
// TODO is this the correct way to match role name from Azure Functions IIS host?
595+
// TODO we can update this check after the new functions model is deployed.
597596
if (value != null && "java".equals(System.getenv("FUNCTIONS_WORKER_RUNTIME"))) {
598597
// special case for Azure Functions
599598
return value.toLowerCase(Locale.ENGLISH);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetr
4141
// triggers loading of java.util.logging (starting with Java 8u231)
4242
// and JBoss/Wildfly need to install their own JUL manager before JUL is initialized.
4343

44+
// TODO we can update this check after the new functions model is deployed.
4445
if (!"java".equals(System.getenv("FUNCTIONS_WORKER_RUNTIME"))) {
4546
// Delay registering and starting AppId retrieval until the connection string becomes
4647
// available for Linux Consumption Plan.
Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,35 @@
2323

2424
import ch.qos.logback.classic.LoggerContext;
2525
import com.azure.monitor.opentelemetry.exporter.implementation.configuration.ConnectionString;
26-
import com.microsoft.applicationinsights.agent.bootstrap.AiLazyConfiguration;
26+
import com.azure.monitor.opentelemetry.exporter.implementation.configuration.StatsbeatConnectionString;
27+
import com.azure.monitor.opentelemetry.exporter.implementation.utils.Strings;
2728
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration;
2829
import com.microsoft.applicationinsights.agent.internal.exporter.AgentLogExporter;
2930
import com.microsoft.applicationinsights.agent.internal.legacyheaders.DelegatingPropagator;
3031
import com.microsoft.applicationinsights.agent.internal.sampling.DelegatingSampler;
3132
import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryClient;
33+
import io.opentelemetry.javaagent.bootstrap.ClassFileTransformerHolder;
34+
import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder;
35+
import java.lang.instrument.ClassFileTransformer;
36+
import java.lang.instrument.Instrumentation;
3237
import java.util.Collections;
3338
import java.util.List;
3439
import javax.annotation.Nullable;
3540
import org.slf4j.Logger;
3641
import org.slf4j.LoggerFactory;
3742

38-
public class LazyConfigurationAccessor implements AiLazyConfiguration.Accessor {
43+
public class AzureFunctionsInitializer implements Runnable {
3944

40-
private static final Logger logger = LoggerFactory.getLogger(LazyConfigurationAccessor.class);
45+
private static final Logger startupLogger =
46+
LoggerFactory.getLogger("com.microsoft.applicationinsights.agent");
47+
48+
private static final Logger logger = LoggerFactory.getLogger(AzureFunctionsInitializer.class);
4149

4250
private final TelemetryClient telemetryClient;
4351
private final AgentLogExporter agentLogExporter;
4452
private final AppIdSupplier appIdSupplier;
4553

46-
public LazyConfigurationAccessor(
54+
public AzureFunctionsInitializer(
4755
TelemetryClient telemetryClient,
4856
AgentLogExporter agentLogExporter,
4957
AppIdSupplier appIdSupplier) {
@@ -53,30 +61,50 @@ public LazyConfigurationAccessor(
5361
}
5462

5563
@Override
56-
public void lazyLoad() {
57-
String instrumentationKey = telemetryClient.getInstrumentationKey();
58-
String roleName = telemetryClient.getRoleName();
59-
if (instrumentationKey != null
60-
&& !instrumentationKey.isEmpty()
61-
&& roleName != null
62-
&& !roleName.isEmpty()) {
64+
public void run() {
65+
if (!isAgentEnabled()) {
66+
disableBytecodeInstrumentation();
6367
return;
6468
}
6569

66-
if (!isAgentEnabled()) {
67-
return;
70+
String selfDiagnosticsLevel = System.getenv("APPLICATIONINSIGHTS_SELF_DIAGNOSTICS_LEVEL");
71+
String connectionString = System.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING");
72+
String instrumentationKey = System.getenv("APPINSIGHTS_INSTRUMENTATIONKEY");
73+
String websiteSiteName = System.getenv("WEBSITE_SITE_NAME");
74+
String instrumentationLoggingLevel =
75+
System.getenv("APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL");
76+
77+
logger.debug("APPLICATIONINSIGHTS_SELF_DIAGNOSTICS_LEVEL: {}", selfDiagnosticsLevel);
78+
logger.debug("APPLICATIONINSIGHTS_CONNECTION_STRING: {}", connectionString);
79+
if (Strings.isNullOrEmpty(connectionString)) {
80+
logger.debug("APPINSIGHTS_INSTRUMENTATIONKEY: {}", instrumentationKey);
6881
}
82+
logger.debug("WEBSITE_SITE_NAME: {}", websiteSiteName);
83+
logger.debug(
84+
"APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL: {}", instrumentationLoggingLevel);
6985

70-
setConnectionString(
71-
System.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"),
72-
System.getenv("APPINSIGHTS_INSTRUMENTATIONKEY"));
73-
setWebsiteSiteName(System.getenv("WEBSITE_SITE_NAME"));
74-
setSelfDiagnosticsLevel(System.getenv("APPLICATIONINSIGHTS_SELF_DIAGNOSTICS_LEVEL"));
86+
setConnectionString(connectionString, instrumentationKey);
87+
setWebsiteSiteName(websiteSiteName);
88+
setSelfDiagnosticsLevel(selfDiagnosticsLevel);
7589
agentLogExporter.setThreshold(
76-
Configuration.LoggingInstrumentation.getSeverity(
77-
System.getenv("APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL")));
90+
Configuration.LoggingInstrumentation.getSeverity(instrumentationLoggingLevel));
91+
92+
startupLogger.info(
93+
"ApplicationInsights Java Agent specialization complete for Azure Functions placeholder");
94+
}
95+
96+
private static void disableBytecodeInstrumentation() {
97+
Instrumentation instrumentation = InstrumentationHolder.getInstrumentation();
98+
ClassFileTransformer transformer = ClassFileTransformerHolder.getClassFileTransformer();
99+
if (instrumentation == null || transformer == null) {
100+
return;
101+
}
102+
if (instrumentation.removeTransformer(transformer)) {
103+
ClassFileTransformerHolder.setClassFileTransformer(null);
104+
}
78105
}
79106

107+
// visible for testing
80108
void setConnectionString(@Nullable String connectionString, @Nullable String instrumentationKey) {
81109
if (connectionString != null && !connectionString.isEmpty()) {
82110
setValue(connectionString);
@@ -92,24 +120,23 @@ void setConnectionString(@Nullable String connectionString, @Nullable String ins
92120
}
93121

94122
private void setValue(String value) {
95-
// passing nulls because lazy configuration doesn't support manual statsbeat overrides
96-
telemetryClient.setConnectionString(ConnectionString.parse(value));
123+
ConnectionString connectionString = ConnectionString.parse(value);
124+
telemetryClient.updateConnectionString(connectionString);
125+
telemetryClient.updateStatsbeatConnectionString(
126+
StatsbeatConnectionString.create(connectionString, null, null));
127+
97128
// now that we know the user has opted in to tracing, we need to init the propagator and sampler
98129
DelegatingPropagator.getInstance().setUpStandardDelegate(Collections.emptyList(), false);
99130
// TODO handle APPLICATIONINSIGHTS_SAMPLING_PERCENTAGE
100131
DelegatingSampler.getInstance().setAlwaysOnDelegate();
101-
logger.debug("Set connection string {} lazily for the Azure Function Consumption Plan.", value);
102132

103133
// start app id retrieval after the connection string becomes available.
104134
appIdSupplier.startAppIdRetrieval();
105135
}
106136

107137
void setWebsiteSiteName(@Nullable String websiteSiteName) {
108138
if (websiteSiteName != null && !websiteSiteName.isEmpty()) {
109-
telemetryClient.setRoleName(websiteSiteName);
110-
logger.debug(
111-
"Set WEBSITE_SITE_NAME: {} lazily for the Azure Function Consumption Plan.",
112-
websiteSiteName);
139+
telemetryClient.updateRoleName(websiteSiteName);
113140
}
114141
}
115142

@@ -118,8 +145,6 @@ static void setSelfDiagnosticsLevel(@Nullable String loggingLevel) {
118145
return;
119146
}
120147

121-
logger.debug("setting APPLICATIONINSIGHTS_SELF_DIAGNOSTICS_LEVEL to {}", loggingLevel);
122-
123148
LoggingLevelConfigurator configurator;
124149
try {
125150
configurator = new LoggingLevelConfigurator(loggingLevel);
@@ -135,15 +160,14 @@ static void setSelfDiagnosticsLevel(@Nullable String loggingLevel) {
135160
// also need to update any previously created loggers
136161
List<ch.qos.logback.classic.Logger> loggerList = loggerContext.getLoggerList();
137162
loggerList.forEach(configurator::updateLoggerLevel);
138-
logger.debug("self-diagnostics logging level has been updated.");
139163
}
140164

141165
// since the agent is already running at this point, this really just determines whether the
142166
// telemetry is sent to the ingestion service or not (essentially behaving to the user as if the
143167
// agent is not enabled)
144168
static boolean isAgentEnabled() {
145169
String enableAgent = System.getenv("APPLICATIONINSIGHTS_ENABLE_AGENT");
146-
boolean enableAgentDefault = Boolean.parseBoolean(System.getProperty("LazySetOptIn"));
170+
boolean enableAgentDefault = Boolean.getBoolean("LazySetOptIn");
147171
logger.debug("APPLICATIONINSIGHTS_ENABLE_AGENT: {}", enableAgent);
148172
logger.debug("LazySetOptIn: {}", enableAgentDefault);
149173
return isAgentEnabled(enableAgent, enableAgentDefault);

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,16 @@ private void configureAppSvc() {
130130

131131
private void configureFunctions() {
132132
Logger rootLogger = loggerContext.getLogger(ROOT_LOGGER_NAME);
133-
rootLogger.addAppender(configureConsoleAppender());
133+
rootLogger.addAppender(configureFileAppender());
134134
Logger diagnosticLogger = loggerContext.getLogger(DiagnosticsHelper.DIAGNOSTICS_LOGGER_NAME);
135135
diagnosticLogger.setLevel(Level.INFO);
136136
diagnosticLogger.setAdditive(false);
137-
Appender<ILoggingEvent> appender = configureConsoleAppender();
138-
diagnosticLogger.addAppender(appender);
137+
Appender<ILoggingEvent> diagnosticAppender = configureConsoleAppender();
138+
diagnosticLogger.addAppender(diagnosticAppender);
139139

140140
// errors reported by other loggers should also go to diagnostic log
141141
// (level filter for these is applied in ApplicationInsightsDiagnosticsLogFilter)
142-
rootLogger.addAppender(appender);
142+
rootLogger.addAppender(diagnosticAppender);
143143

144144
loggingLevelConfigurator.initLoggerLevels(loggerContext);
145145
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ public void run() {
105105
"Connection string from the JSON config file is overriding the previously configured connection string.");
106106
ConnectionString connectionString =
107107
ConnectionString.parse(newRpConfiguration.connectionString);
108-
telemetryClient.setConnectionString(connectionString);
109-
telemetryClient.setStatsbeatConnectionString(
108+
telemetryClient.updateConnectionString(connectionString);
109+
telemetryClient.updateStatsbeatConnectionString(
110110
StatsbeatConnectionString.create(
111111
connectionString,
112112
configuration.internal.statsbeat.instrumentationKey,

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import com.azure.monitor.opentelemetry.exporter.implementation.utils.TempDirs;
3636
import com.google.auto.service.AutoService;
3737
import com.microsoft.applicationinsights.agent.bootstrap.AiAppId;
38-
import com.microsoft.applicationinsights.agent.bootstrap.AiLazyConfiguration;
38+
import com.microsoft.applicationinsights.agent.bootstrap.AzureFunctions;
3939
import com.microsoft.applicationinsights.agent.internal.common.FriendlyException;
4040
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration;
4141
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration.ProcessorConfig;
@@ -114,6 +114,7 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
114114

115115
Configuration config = FirstEntryPoint.getConfiguration();
116116
if (Strings.isNullOrEmpty(config.connectionString)) {
117+
// TODO we can update this check after the new functions model is deployed.
117118
if (!"java".equals(System.getenv("FUNCTIONS_WORKER_RUNTIME"))) {
118119
throw new FriendlyException(
119120
"No connection string provided", "Please provide connection string.");
@@ -185,9 +186,11 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
185186
}
186187

187188
// this is for Azure Function Linux consumption plan support.
189+
// TODO we can update this check after the new functions model is deployed.
188190
if ("java".equals(System.getenv("FUNCTIONS_WORKER_RUNTIME"))) {
189-
AiLazyConfiguration.setAccessor(
190-
new LazyConfigurationAccessor(
191+
AzureFunctions.setup(
192+
() -> telemetryClient.getConnectionString() != null,
193+
new AzureFunctionsInitializer(
191194
telemetryClient, SecondEntryPoint.agentLogExporter, appIdSupplier));
192195
}
193196

0 commit comments

Comments
 (0)