Repository: megaease/easeagent Branch: master Commit: ec7bd5f578ef Files: 1354 Total size: 4.6 MB Directory structure: gitextract_4u5zqd_x/ ├── .editorconfig ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ ├── build.yml │ └── license-checker.yml ├── .gitignore ├── .licenserc.yaml ├── AOSP-Checkstyles.xml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── build/ │ ├── pom.xml │ └── src/ │ ├── assembly/ │ │ └── src.xml │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── StartBootstrap.java │ └── resources/ │ ├── agent-to-cloud_1.0.properties │ ├── agent.properties │ ├── easeagent-log4j2.xml │ └── user-minimal-cfg.properties ├── config/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── config/ │ │ ├── AutoRefreshConfigItem.java │ │ ├── CompatibilityConversion.java │ │ ├── ConfigAware.java │ │ ├── ConfigFactory.java │ │ ├── ConfigLoader.java │ │ ├── ConfigManagerMXBean.java │ │ ├── ConfigNotifier.java │ │ ├── ConfigPropertiesUtils.java │ │ ├── ConfigUtils.java │ │ ├── Configs.java │ │ ├── GlobalConfigs.java │ │ ├── JarFileConfigLoader.java │ │ ├── OtelSdkConfigs.java │ │ ├── PluginConfig.java │ │ ├── PluginConfigManager.java │ │ ├── PluginProperty.java │ │ ├── PluginSourceConfig.java │ │ ├── ValidateUtils.java │ │ ├── WrappedConfigManager.java │ │ ├── report/ │ │ │ ├── ReportConfigAdapter.java │ │ │ └── ReportConfigConst.java │ │ └── yaml/ │ │ └── YamlReader.java │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── config/ │ │ ├── CompatibilityConversionTest.java │ │ ├── ConfigFactoryTest.java │ │ ├── ConfigPropertiesUtilsTest.java │ │ ├── ConfigUtilsTest.java │ │ ├── ConfigsTest.java │ │ ├── IPluginConfigConstTest.java │ │ ├── JarFileConfigLoaderTest.java │ │ ├── OtelSdkConfigsTest.java │ │ ├── PluginConfigManagerTest.java │ │ ├── PluginConfigTest.java │ │ ├── PluginPropertyTest.java │ │ ├── PluginSourceConfigTest.java │ │ ├── ValidateUtilsTest.java │ │ ├── report/ │ │ │ └── ReportConfigAdapterTest.java │ │ └── yaml/ │ │ └── YamlReaderTest.java │ └── resources/ │ ├── agent.properties │ ├── agent.yaml │ ├── easeagent_config.jar │ ├── user-spec.properties │ ├── user-spec2.properties │ └── user.properties ├── context/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── context/ │ │ ├── AsyncContextImpl.java │ │ ├── ContextManager.java │ │ ├── GlobalContext.java │ │ ├── ProgressFieldsManager.java │ │ ├── RetBound.java │ │ ├── SessionContext.java │ │ └── log/ │ │ ├── LoggerFactoryImpl.java │ │ ├── LoggerImpl.java │ │ └── LoggerMdc.java │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── context/ │ │ ├── AsyncContextImplTest.java │ │ ├── ContextManagerTest.java │ │ ├── GlobalContextTest.java │ │ ├── ProgressFieldsManagerTest.java │ │ ├── RetBoundTest.java │ │ ├── SessionContextTest.java │ │ └── log/ │ │ ├── LoggerFactoryImplTest.java │ │ ├── LoggerImplTest.java │ │ └── LoggerMdcTest.java │ └── resources/ │ └── log4j2.xml ├── core/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── core/ │ │ │ ├── AppendBootstrapClassLoaderSearch.java │ │ │ ├── Bootstrap.java │ │ │ ├── GlobalAgentHolder.java │ │ │ ├── config/ │ │ │ │ ├── CanaryListUpdateAgentHttpHandler.java │ │ │ │ ├── CanaryUpdateAgentHttpHandler.java │ │ │ │ ├── ConfigsUpdateAgentHttpHandler.java │ │ │ │ ├── PluginPropertiesHttpHandler.java │ │ │ │ ├── PluginPropertyHttpHandler.java │ │ │ │ └── ServiceUpdateAgentHttpHandler.java │ │ │ ├── health/ │ │ │ │ └── HealthProvider.java │ │ │ ├── info/ │ │ │ │ ├── AgentInfoFactory.java │ │ │ │ └── AgentInfoProvider.java │ │ │ ├── plugin/ │ │ │ │ ├── BaseLoader.java │ │ │ │ ├── BridgeDispatcher.java │ │ │ │ ├── CommonInlineAdvice.java │ │ │ │ ├── Dispatcher.java │ │ │ │ ├── PluginLoader.java │ │ │ │ ├── annotation/ │ │ │ │ │ ├── EaseAgentInstrumented.java │ │ │ │ │ └── Index.java │ │ │ │ ├── interceptor/ │ │ │ │ │ ├── InterceptorPluginDecorator.java │ │ │ │ │ ├── ProviderChain.java │ │ │ │ │ └── ProviderPluginDecorator.java │ │ │ │ ├── matcher/ │ │ │ │ │ ├── ClassLoaderMatcherConvert.java │ │ │ │ │ ├── ClassMatcherConvert.java │ │ │ │ │ ├── ClassTransformation.java │ │ │ │ │ ├── Converter.java │ │ │ │ │ ├── MethodMatcherConvert.java │ │ │ │ │ └── MethodTransformation.java │ │ │ │ ├── registry/ │ │ │ │ │ ├── AdviceRegistry.java │ │ │ │ │ └── PluginRegistry.java │ │ │ │ └── transformer/ │ │ │ │ ├── AnnotationTransformer.java │ │ │ │ ├── CompoundPluginTransformer.java │ │ │ │ ├── DynamicFieldAdvice.java │ │ │ │ ├── DynamicFieldTransformer.java │ │ │ │ ├── ForAdviceTransformer.java │ │ │ │ ├── TypeFieldTransformer.java │ │ │ │ ├── advice/ │ │ │ │ │ ├── AgentAdvice.java │ │ │ │ │ ├── AgentForAdvice.java │ │ │ │ │ ├── AgentJavaConstantValue.java │ │ │ │ │ ├── BypassMethodVisitor.java │ │ │ │ │ └── MethodIdentityJavaConstant.java │ │ │ │ └── classloader/ │ │ │ │ └── CompoundClassloader.java │ │ │ └── utils/ │ │ │ ├── AgentArray.java │ │ │ ├── ContextUtils.java │ │ │ ├── JsonUtil.java │ │ │ ├── MutableObject.java │ │ │ ├── ServletUtils.java │ │ │ └── TextUtils.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ └── com.megaease.easeagent.plugin.bean.BeanProvider │ │ └── version.txt │ └── test/ │ └── java/ │ └── com/ │ └── megaease/ │ └── easeagent/ │ └── core/ │ ├── AppendBootstrapClassLoaderSearchTest.java │ ├── BootstrapTest.java │ ├── HttpServerTest.java │ ├── info/ │ │ └── AgentInfoFactoryTest.java │ ├── instrument/ │ │ ├── ClinitMethodTransformTest.java │ │ ├── NewInstanceMethodTransformTest.java │ │ ├── NonStaticMethodTransformTest.java │ │ ├── OrchestrationTransformTest.java │ │ ├── StaticMethodTransformTest.java │ │ ├── TestContext.java │ │ ├── TestPlugin.java │ │ └── TransformTestBase.java │ ├── matcher/ │ │ ├── ClassLoaderMatcherTest.java │ │ ├── ClassMatcherTest.java │ │ └── MethodMatcherTest.java │ ├── plugin/ │ │ └── PluginLoaderTest.java │ └── utils/ │ └── AgentAttachmentRule.java ├── doc/ │ ├── add-plugin-demo.md │ ├── benchmark.md │ ├── context.md │ ├── criteria-for-configuring-priorities.md │ ├── development-guide.md │ ├── how-to-use/ │ │ ├── megacloud-config.md │ │ ├── use-in-docker.md │ │ └── use-on-host.md │ ├── matcher-DSL.md │ ├── metric-api.md │ ├── plugin-unit-test.md │ ├── prometheus-metric-schedule.md │ ├── report-development-guide.md │ ├── spring-boot-3.x.x-demo.md │ ├── spring-boot-upgrade.md │ ├── spring-petclinic-demo.md │ ├── tracing-api.md │ └── user-manual.md ├── httpserver/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── megaease/ │ └── easeagent/ │ └── httpserver/ │ ├── HttpRequest.java │ ├── HttpResponse.java │ ├── IHttpHandler.java │ ├── IHttpServer.java │ ├── jdk/ │ │ ├── AgentHttpServerV2.java │ │ ├── RootContextHandler.java │ │ └── UriResource.java │ ├── nano/ │ │ ├── AgentHttpHandler.java │ │ ├── AgentHttpHandlerProvider.java │ │ └── AgentHttpServer.java │ └── nanohttpd/ │ ├── protocols/ │ │ └── http/ │ │ ├── ClientHandler.java │ │ ├── HTTPSession.java │ │ ├── IHTTPSession.java │ │ ├── NanoHTTPD.java │ │ ├── ServerRunnable.java │ │ ├── content/ │ │ │ ├── ContentType.java │ │ │ ├── Cookie.java │ │ │ └── CookieHandler.java │ │ ├── request/ │ │ │ └── Method.java │ │ ├── response/ │ │ │ ├── ChunkedOutputStream.java │ │ │ ├── IStatus.java │ │ │ ├── Response.java │ │ │ └── Status.java │ │ ├── sockets/ │ │ │ ├── DefaultServerSocketFactory.java │ │ │ └── SecureServerSocketFactory.java │ │ ├── tempfiles/ │ │ │ ├── DefaultTempFile.java │ │ │ ├── DefaultTempFileManager.java │ │ │ ├── DefaultTempFileManagerFactory.java │ │ │ ├── ITempFile.java │ │ │ └── ITempFileManager.java │ │ └── threading/ │ │ ├── DefaultAsyncRunner.java │ │ └── IAsyncRunner.java │ ├── router/ │ │ └── RouterNanoHTTPD.java │ └── util/ │ ├── IFactory.java │ ├── IFactoryThrowing.java │ ├── IHandler.java │ └── ServerRunner.java ├── loader/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ ├── EaseAgentClassLoader.java │ │ ├── JarCache.java │ │ ├── Main.java │ │ └── StringSequence.java │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── MainTest.java │ └── resources/ │ └── test-mock-load.jar ├── log4j2/ │ ├── README.md │ ├── log4j2-api/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── log4j2/ │ │ ├── ClassLoaderUtils.java │ │ ├── ClassloaderSupplier.java │ │ ├── FinalClassloaderSupplier.java │ │ ├── Logger.java │ │ ├── LoggerFactory.java │ │ ├── MDC.java │ │ ├── api/ │ │ │ ├── AgentLogger.java │ │ │ ├── AgentLoggerFactory.java │ │ │ ├── ILevel.java │ │ │ └── Mdc.java │ │ └── exception/ │ │ └── Log4j2Exception.java │ ├── log4j2-impl/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── log4j2/ │ │ └── impl/ │ │ ├── AgentLoggerProxy.java │ │ ├── LoggerProxyFactory.java │ │ ├── MdcProxy.java │ │ └── Slf4jLogger.java │ └── pom.xml ├── metrics/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── metrics/ │ │ │ ├── AgentScheduledReporter.java │ │ │ ├── AutoRefreshReporter.java │ │ │ ├── MetricBeanProviderImpl.java │ │ │ ├── MetricProviderImpl.java │ │ │ ├── MetricRegistryService.java │ │ │ ├── PrometheusAgentHttpHandler.java │ │ │ ├── config/ │ │ │ │ ├── MetricsCollectorConfig.java │ │ │ │ ├── MetricsConfig.java │ │ │ │ └── PluginMetricsConfig.java │ │ │ ├── converter/ │ │ │ │ ├── AbstractConverter.java │ │ │ │ ├── Converter.java │ │ │ │ ├── ConverterAdapter.java │ │ │ │ ├── EaseAgentPrometheusExports.java │ │ │ │ ├── IgnoreOutputException.java │ │ │ │ ├── KeyType.java │ │ │ │ └── MetricsAdditionalAttributes.java │ │ │ ├── impl/ │ │ │ │ ├── CounterImpl.java │ │ │ │ ├── GaugeImpl.java │ │ │ │ ├── HistogramImpl.java │ │ │ │ ├── MeterImpl.java │ │ │ │ ├── MetricInstance.java │ │ │ │ ├── MetricRegistryImpl.java │ │ │ │ ├── SnapshotImpl.java │ │ │ │ └── TimerImpl.java │ │ │ ├── jvm/ │ │ │ │ ├── JvmBeanProvider.java │ │ │ │ ├── gc/ │ │ │ │ │ └── JVMGCMetricV2.java │ │ │ │ └── memory/ │ │ │ │ └── JVMMemoryMetricV2.java │ │ │ └── model/ │ │ │ └── JVMMemoryGaugeMetricModel.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.megaease.easeagent.plugin.bean.BeanProvider │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── metrics/ │ │ ├── MetricProviderImplTest.java │ │ ├── MetricRegistryServiceTest.java │ │ ├── PrometheusAgentHttpHandlerTest.java │ │ ├── TestConst.java │ │ ├── config/ │ │ │ └── PluginMetricsConfigTest.java │ │ ├── converter/ │ │ │ ├── AbstractConverterTest.java │ │ │ ├── ConverterAdapterTest.java │ │ │ ├── IgnoreOutputExceptionTest.java │ │ │ └── MetricsAdditionalAttributesTest.java │ │ ├── impl/ │ │ │ ├── CounterImplTest.java │ │ │ ├── GaugeImplTest.java │ │ │ ├── HistogramImplTest.java │ │ │ ├── MeterImplTest.java │ │ │ ├── MetricInstanceTest.java │ │ │ ├── MetricRegistryImplTest.java │ │ │ ├── MetricRegistryMock.java │ │ │ ├── MetricTestUtils.java │ │ │ ├── MockClock.java │ │ │ ├── SnapshotImplTest.java │ │ │ └── TimerImplTest.java │ │ └── jvm/ │ │ └── memory/ │ │ └── JVMMemoryMetricV2Test.java │ └── resources/ │ ├── log4j2.xml │ └── mock_agent.properties ├── mock/ │ ├── config-mock/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ ├── config/ │ │ │ │ └── MockConfigLoader.java │ │ │ └── mock/ │ │ │ └── config/ │ │ │ └── MockConfig.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── mock/ │ │ │ └── config/ │ │ │ └── MockConfigTest.java │ │ └── resources/ │ │ ├── mock_agent.properties │ │ └── mock_agent.yaml │ ├── context-mock/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── mock/ │ │ └── context/ │ │ ├── MockContext.java │ │ └── MockContextManager.java │ ├── log4j2-mock/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── megaease/ │ │ │ │ └── easeagent/ │ │ │ │ └── mock/ │ │ │ │ └── log4j2/ │ │ │ │ ├── AllUrlsSupplier.java │ │ │ │ ├── DirUrlsSupplier.java │ │ │ │ ├── JarPathUrlsSupplier.java │ │ │ │ ├── JarUrlsSupplier.java │ │ │ │ ├── URLClassLoaderSupplier.java │ │ │ │ └── UrlSupplier.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── com.megaease.easeagent.log4j2.ClassloaderSupplier │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── log4j2/ │ │ │ └── impl/ │ │ │ ├── AgentLoggerFactoryTest.java │ │ │ └── MDCTest.java │ │ └── resources/ │ │ └── log4j2.xml │ ├── metrics-mock/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── mock/ │ │ │ └── metrics/ │ │ │ ├── MetricTestUtils.java │ │ │ └── MockMetricProvider.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.megaease.easeagent.mock.utils.MockProvider │ ├── plugin-api-mock/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── mock/ │ │ │ └── plugin/ │ │ │ └── api/ │ │ │ ├── MockEaseAgent.java │ │ │ ├── junit/ │ │ │ │ ├── AfterStatement.java │ │ │ │ ├── BeforeStatement.java │ │ │ │ ├── EaseAgentJunit4ClassRunner.java │ │ │ │ └── ScopeMustBeCloseException.java │ │ │ └── utils/ │ │ │ ├── ConfigTestUtils.java │ │ │ ├── ContextUtils.java │ │ │ ├── InterceptorTestUtils.java │ │ │ ├── SpanTestUtils.java │ │ │ └── TagVerifier.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── mock/ │ │ │ └── plugin/ │ │ │ └── api/ │ │ │ ├── TestContext.java │ │ │ ├── TestEaseAgent.java │ │ │ ├── demo/ │ │ │ │ ├── InterceptorTest.java │ │ │ │ ├── M1MetricCollect.java │ │ │ │ └── MockEaseAgentTest.java │ │ │ └── utils/ │ │ │ └── ConfigTestUtilsTest.java │ │ └── resources/ │ │ └── log4j2.xml │ ├── pom.xml │ ├── report-mock/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── mock/ │ │ │ └── report/ │ │ │ ├── JsonReporter.java │ │ │ ├── MetricFlushable.java │ │ │ ├── MockAtomicReferenceReportSpanReport.java │ │ │ ├── MockReport.java │ │ │ ├── MockSpan.java │ │ │ ├── MockSpanReport.java │ │ │ └── impl/ │ │ │ ├── LastJsonReporter.java │ │ │ └── ZipkinMockSpanImpl.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── mock/ │ │ │ └── report/ │ │ │ └── MockReportTest.java │ │ └── resources/ │ │ └── log4j2.xml │ ├── utils-mock/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── mock/ │ │ └── utils/ │ │ ├── JdkHttpServer.java │ │ ├── MockProvider.java │ │ └── MockSystemEnv.java │ └── zipkin-mock/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── brave/ │ │ │ │ ├── TracerTestUtils.java │ │ │ │ └── internal/ │ │ │ │ └── collect/ │ │ │ │ └── WeakConcurrentMapTestUtils.java │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── mock/ │ │ │ └── zipkin/ │ │ │ └── MockTracingProvider.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.megaease.easeagent.mock.utils.MockProvider │ └── test/ │ └── java/ │ └── com/ │ └── megaease/ │ └── easeagent/ │ └── mock/ │ └── zipkin/ │ └── MockTracingProviderTest.java ├── plugin-api/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ ├── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ ├── AgentPlugin.java │ │ │ ├── AppendBootstrapLoader.java │ │ │ ├── CodeVersion.java │ │ │ ├── Ordered.java │ │ │ ├── Points.java │ │ │ ├── annotation/ │ │ │ │ ├── AdviceTo.java │ │ │ │ ├── AdvicesTo.java │ │ │ │ ├── DynamicField.java │ │ │ │ └── Injection.java │ │ │ ├── api/ │ │ │ │ ├── Cleaner.java │ │ │ │ ├── Context.java │ │ │ │ ├── InitializeContext.java │ │ │ │ ├── ProgressFields.java │ │ │ │ ├── Reporter.java │ │ │ │ ├── config/ │ │ │ │ │ ├── AutoRefreshConfigSupplier.java │ │ │ │ │ ├── AutoRefreshPluginConfig.java │ │ │ │ │ ├── AutoRefreshPluginConfigImpl.java │ │ │ │ │ ├── AutoRefreshPluginConfigRegistry.java │ │ │ │ │ ├── ChangeItem.java │ │ │ │ │ ├── Config.java │ │ │ │ │ ├── ConfigChangeListener.java │ │ │ │ │ ├── ConfigConst.java │ │ │ │ │ ├── Const.java │ │ │ │ │ ├── IConfigFactory.java │ │ │ │ │ ├── IPluginConfig.java │ │ │ │ │ └── PluginConfigChangeListener.java │ │ │ │ ├── context/ │ │ │ │ │ ├── AsyncContext.java │ │ │ │ │ ├── ContextCons.java │ │ │ │ │ ├── ContextUtils.java │ │ │ │ │ ├── IContextManager.java │ │ │ │ │ └── RequestContext.java │ │ │ │ ├── dispatcher/ │ │ │ │ │ └── IDispatcher.java │ │ │ │ ├── health/ │ │ │ │ │ └── AgentHealth.java │ │ │ │ ├── logging/ │ │ │ │ │ ├── AccessLogInfo.java │ │ │ │ │ ├── ILoggerFactory.java │ │ │ │ │ ├── Logger.java │ │ │ │ │ └── Mdc.java │ │ │ │ ├── metric/ │ │ │ │ │ ├── Counter.java │ │ │ │ │ ├── Gauge.java │ │ │ │ │ ├── Histogram.java │ │ │ │ │ ├── Meter.java │ │ │ │ │ ├── Metric.java │ │ │ │ │ ├── MetricProvider.java │ │ │ │ │ ├── MetricRegistry.java │ │ │ │ │ ├── MetricRegistrySupplier.java │ │ │ │ │ ├── MetricSupplier.java │ │ │ │ │ ├── ServiceMetric.java │ │ │ │ │ ├── ServiceMetricRegistry.java │ │ │ │ │ ├── ServiceMetricSupplier.java │ │ │ │ │ ├── Snapshot.java │ │ │ │ │ ├── Timer.java │ │ │ │ │ └── name/ │ │ │ │ │ ├── ConverterType.java │ │ │ │ │ ├── MetricField.java │ │ │ │ │ ├── MetricName.java │ │ │ │ │ ├── MetricSubType.java │ │ │ │ │ ├── MetricType.java │ │ │ │ │ ├── MetricValueFetcher.java │ │ │ │ │ ├── NameFactory.java │ │ │ │ │ └── Tags.java │ │ │ │ ├── middleware/ │ │ │ │ │ ├── MiddlewareConstants.java │ │ │ │ │ ├── Redirect.java │ │ │ │ │ ├── RedirectProcessor.java │ │ │ │ │ ├── ResourceConfig.java │ │ │ │ │ └── Type.java │ │ │ │ ├── otlp/ │ │ │ │ │ └── common/ │ │ │ │ │ ├── AgentAttributes.java │ │ │ │ │ ├── AgentInstrumentLibInfo.java │ │ │ │ │ ├── AgentLogData.java │ │ │ │ │ ├── AgentLogDataImpl.java │ │ │ │ │ ├── LogMapper.java │ │ │ │ │ ├── OtlpSpanContext.java │ │ │ │ │ └── SemanticKey.java │ │ │ │ └── trace/ │ │ │ │ ├── Extractor.java │ │ │ │ ├── Getter.java │ │ │ │ ├── ITracing.java │ │ │ │ ├── Injector.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessagingRequest.java │ │ │ │ ├── MessagingTracing.java │ │ │ │ ├── Request.java │ │ │ │ ├── Response.java │ │ │ │ ├── Scope.java │ │ │ │ ├── Setter.java │ │ │ │ ├── Span.java │ │ │ │ ├── SpanContext.java │ │ │ │ ├── Tracing.java │ │ │ │ ├── TracingContext.java │ │ │ │ ├── TracingProvider.java │ │ │ │ └── TracingSupplier.java │ │ │ ├── asm/ │ │ │ │ └── Modifier.java │ │ │ ├── async/ │ │ │ │ ├── AgentThreadFactory.java │ │ │ │ ├── ScheduleHelper.java │ │ │ │ ├── ScheduleRunner.java │ │ │ │ ├── ThreadLocalCurrentContext.java │ │ │ │ └── ThreadUtils.java │ │ │ ├── bean/ │ │ │ │ ├── AgentInitializingBean.java │ │ │ │ └── BeanProvider.java │ │ │ ├── bridge/ │ │ │ │ ├── AgentInfo.java │ │ │ │ ├── EaseAgent.java │ │ │ │ ├── NoOpAgentReporter.java │ │ │ │ ├── NoOpCleaner.java │ │ │ │ ├── NoOpConfigFactory.java │ │ │ │ ├── NoOpContext.java │ │ │ │ ├── NoOpDispatcher.java │ │ │ │ ├── NoOpIPluginConfig.java │ │ │ │ ├── NoOpLoggerFactory.java │ │ │ │ ├── NoOpMetrics.java │ │ │ │ ├── NoOpReporter.java │ │ │ │ └── NoOpTracer.java │ │ │ ├── enums/ │ │ │ │ ├── ClassMatch.java │ │ │ │ ├── Operator.java │ │ │ │ ├── Order.java │ │ │ │ └── StringMatch.java │ │ │ ├── field/ │ │ │ │ ├── AgentDynamicFieldAccessor.java │ │ │ │ ├── AgentFieldReflectAccessor.java │ │ │ │ ├── DynamicFieldAccessor.java │ │ │ │ ├── NullObject.java │ │ │ │ └── TypeFieldGetter.java │ │ │ ├── interceptor/ │ │ │ │ ├── AgentInterceptorChain.java │ │ │ │ ├── Interceptor.java │ │ │ │ ├── InterceptorProvider.java │ │ │ │ ├── MethodInfo.java │ │ │ │ └── NonReentrantInterceptor.java │ │ │ ├── matcher/ │ │ │ │ ├── ClassMatcher.java │ │ │ │ ├── IClassMatcher.java │ │ │ │ ├── IMethodMatcher.java │ │ │ │ ├── Matcher.java │ │ │ │ ├── MethodMatcher.java │ │ │ │ ├── loader/ │ │ │ │ │ ├── ClassLoaderMatcher.java │ │ │ │ │ ├── IClassLoaderMatcher.java │ │ │ │ │ └── NegateClassLoaderMatcher.java │ │ │ │ └── operator/ │ │ │ │ ├── AndClassMatcher.java │ │ │ │ ├── AndMethodMatcher.java │ │ │ │ ├── NegateClassMatcher.java │ │ │ │ ├── NegateMethodMatcher.java │ │ │ │ ├── Operator.java │ │ │ │ ├── OrClassMatcher.java │ │ │ │ └── OrMethodMatcher.java │ │ │ ├── processor/ │ │ │ │ ├── BeanUtils.java │ │ │ │ ├── ElementVisitor8.java │ │ │ │ ├── GenerateProviderBean.java │ │ │ │ ├── PluginProcessor.java │ │ │ │ └── RepeatedAnnotationVisitor.java │ │ │ ├── report/ │ │ │ │ ├── AgentReport.java │ │ │ │ ├── ByteWrapper.java │ │ │ │ ├── Call.java │ │ │ │ ├── Callback.java │ │ │ │ ├── EncodedData.java │ │ │ │ ├── Encoder.java │ │ │ │ ├── Packer.java │ │ │ │ ├── Sender.java │ │ │ │ ├── encoder/ │ │ │ │ │ └── JsonEncoder.java │ │ │ │ ├── metric/ │ │ │ │ │ └── MetricReporterFactory.java │ │ │ │ └── tracing/ │ │ │ │ ├── Annotation.java │ │ │ │ ├── Endpoint.java │ │ │ │ ├── ReportSpan.java │ │ │ │ └── ReportSpanImpl.java │ │ │ ├── tools/ │ │ │ │ ├── config/ │ │ │ │ │ └── NameAndSystem.java │ │ │ │ ├── loader/ │ │ │ │ │ └── AgentHelperClassLoader.java │ │ │ │ ├── matcher/ │ │ │ │ │ ├── ClassMatcherUtils.java │ │ │ │ │ └── MethodMatcherUtils.java │ │ │ │ ├── metrics/ │ │ │ │ │ ├── AccessLogServerInfo.java │ │ │ │ │ ├── ErrorPercentModelGauge.java │ │ │ │ │ ├── GaugeMetricModel.java │ │ │ │ │ ├── HttpLog.java │ │ │ │ │ ├── LastMinutesCounterGauge.java │ │ │ │ │ ├── NameFactorySupplier.java │ │ │ │ │ ├── RedisMetric.java │ │ │ │ │ └── ServerMetric.java │ │ │ │ └── trace/ │ │ │ │ ├── BaseHttpClientTracingInterceptor.java │ │ │ │ ├── HttpRequest.java │ │ │ │ ├── HttpResponse.java │ │ │ │ ├── HttpUtils.java │ │ │ │ └── TraceConst.java │ │ │ └── utils/ │ │ │ ├── AdditionalAttributes.java │ │ │ ├── ClassInstance.java │ │ │ ├── ClassUtils.java │ │ │ ├── ImmutableMap.java │ │ │ ├── NoNull.java │ │ │ ├── Pair.java │ │ │ ├── SystemClock.java │ │ │ ├── SystemEnv.java │ │ │ ├── common/ │ │ │ │ ├── DataSize.java │ │ │ │ ├── DataUnit.java │ │ │ │ ├── ExceptionUtil.java │ │ │ │ ├── HostAddress.java │ │ │ │ ├── JsonUtil.java │ │ │ │ ├── StringUtils.java │ │ │ │ └── WeakConcurrentMap.java │ │ │ └── jackson/ │ │ │ └── annotation/ │ │ │ └── JsonProperty.java │ │ └── io/ │ │ └── opentelemetry/ │ │ └── sdk/ │ │ └── resources/ │ │ └── EaseAgentResource.java │ └── test/ │ └── java/ │ └── com/ │ └── megaease/ │ └── easeagent/ │ └── plugin/ │ ├── api/ │ │ ├── MockSystemEnv.java │ │ ├── ProgressFieldsTest.java │ │ ├── metric/ │ │ │ └── name/ │ │ │ └── NameFactoryTest.java │ │ └── middleware/ │ │ ├── RedirectProcessorTest.java │ │ ├── RedirectTest.java │ │ └── ResourceConfigTest.java │ ├── async/ │ │ └── ThreadLocalCurrentContextTest.java │ └── tools/ │ └── config/ │ └── AutoRefreshConfigSupplierTest.java ├── plugins/ │ ├── async/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ ├── AsyncPlugin.java │ │ │ ├── advice/ │ │ │ │ ├── CrossThreadAdvice.java │ │ │ │ └── ReactSchedulersAdvice.java │ │ │ └── interceptor/ │ │ │ └── RunnableInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── interceptor/ │ │ │ └── RunnableInterceptorTest.java │ │ └── resources/ │ │ └── log4j2.xml │ ├── dubbo/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── dubbo/ │ │ │ ├── AlibabaDubboCtxUtils.java │ │ │ ├── ApacheDubboCtxUtils.java │ │ │ ├── DubboMetricTags.java │ │ │ ├── DubboPlugin.java │ │ │ ├── DubboTraceTags.java │ │ │ ├── advice/ │ │ │ │ ├── AlibabaDubboAdvice.java │ │ │ │ ├── AlibabaDubboResponseFutureAdvice.java │ │ │ │ └── ApacheDubboAdvice.java │ │ │ ├── config/ │ │ │ │ └── DubboTraceConfig.java │ │ │ └── interceptor/ │ │ │ ├── DubboBaseInterceptor.java │ │ │ ├── metrics/ │ │ │ │ ├── DubboBaseMetricsInterceptor.java │ │ │ │ ├── DubboMetrics.java │ │ │ │ ├── alibaba/ │ │ │ │ │ ├── AlibabaDubboAsyncMetricsInterceptor.java │ │ │ │ │ ├── AlibabaDubboMetricsCallback.java │ │ │ │ │ └── AlibabaDubboMetricsInterceptor.java │ │ │ │ └── apache/ │ │ │ │ ├── ApacheDubboMetricsAsyncCallback.java │ │ │ │ └── ApacheDubboMetricsInterceptor.java │ │ │ └── trace/ │ │ │ ├── alibaba/ │ │ │ │ ├── AlibabaDubboAsyncTraceInterceptor.java │ │ │ │ ├── AlibabaDubboClientRequest.java │ │ │ │ ├── AlibabaDubboServerRequest.java │ │ │ │ ├── AlibabaDubboTraceCallback.java │ │ │ │ └── AlibabaDubboTraceInterceptor.java │ │ │ └── apache/ │ │ │ ├── ApacheDubboClientRequest.java │ │ │ ├── ApacheDubboServerRequest.java │ │ │ ├── ApacheDubboTraceCallback.java │ │ │ └── ApacheDubboTraceInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── dubbo/ │ │ │ └── interceptor/ │ │ │ ├── AlibabaDubboBaseTest.java │ │ │ ├── ApacheDubboBaseTest.java │ │ │ ├── metrics/ │ │ │ │ ├── alibaba/ │ │ │ │ │ ├── AlibabaDubboAsyncMetricsInterceptorTest.java │ │ │ │ │ └── AlibabaDubboMetricsInterceptorTest.java │ │ │ │ └── apache/ │ │ │ │ └── ApacheDubboMetricsInterceptorTest.java │ │ │ └── trace/ │ │ │ ├── alibaba/ │ │ │ │ ├── AlibabaDubboAsyncTraceInterceptorTest.java │ │ │ │ └── AlibabaDubboTraceInterceptorTest.java │ │ │ └── apache/ │ │ │ └── ApacheDubboTraceInterceptorTest.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── elasticsearch/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── elasticsearch/ │ │ │ ├── ElasticsearchPlugin.java │ │ │ ├── ElasticsearchRedirectPlugin.java │ │ │ ├── advice/ │ │ │ │ └── SpringElasticsearchAdvice.java │ │ │ ├── interceptor/ │ │ │ │ ├── AsyncResponse4MetricsListener.java │ │ │ │ ├── AsyncResponse4TraceListener.java │ │ │ │ ├── ElasticsearchBaseInterceptor.java │ │ │ │ ├── ElasticsearchBaseMetricsInterceptor.java │ │ │ │ ├── ElasticsearchBaseTraceInterceptor.java │ │ │ │ ├── ElasticsearchCtxUtils.java │ │ │ │ ├── ElasticsearchMetric.java │ │ │ │ ├── ElasticsearchPerformRequestAsync4MetricsInterceptor.java │ │ │ │ ├── ElasticsearchPerformRequestAsync4TraceInterceptor.java │ │ │ │ ├── ElasticsearchPerformRequestMetricsInterceptor.java │ │ │ │ ├── ElasticsearchPerformRequestTraceInterceptor.java │ │ │ │ └── redirect/ │ │ │ │ └── SpringElasticsearchInterceptor.java │ │ │ └── points/ │ │ │ ├── ElasticsearchPerformRequestAsyncPoints.java │ │ │ └── ElasticsearchPerformRequestPoints.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── plugin/ │ │ └── elasticsearch/ │ │ └── interceptor/ │ │ ├── ElasticsearchBaseTest.java │ │ ├── ElasticsearchCtxUtilsTest.java │ │ ├── ElasticsearchPerformRequestAsyncMetricsInterceptorTest.java │ │ ├── ElasticsearchPerformRequestAsyncTraceInterceptorTest.java │ │ ├── ElasticsearchPerformRequestMetricsInterceptorTest.java │ │ └── ElasticsearchPerformRequestTraceInterceptorTest.java │ ├── healthy/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── healthy/ │ │ │ ├── HealthPlugin.java │ │ │ ├── OnApplicationEventInterceptor.java │ │ │ └── SpringApplicationAdminMXBeanRegistrarAdvice.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── plugin/ │ │ └── healthy/ │ │ └── OnApplicationEventInterceptorTest.java │ ├── httpclient/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── httpclient/ │ │ │ ├── ForwardedPlugin.java │ │ │ ├── HttpClientPlugin.java │ │ │ ├── advice/ │ │ │ │ ├── HttpClient5AsyncAdvice.java │ │ │ │ ├── HttpClient5DoExecuteAdvice.java │ │ │ │ └── HttpClientDoExecuteAdvice.java │ │ │ └── interceptor/ │ │ │ ├── HttpClient5AsyncForwardedInterceptor.java │ │ │ ├── HttpClient5AsyncTracingInterceptor.java │ │ │ ├── HttpClient5DoExecuteForwardedInterceptor.java │ │ │ ├── HttpClient5DoExecuteInterceptor.java │ │ │ ├── HttpClientDoExecuteForwardedInterceptor.java │ │ │ └── HttpClientDoExecuteInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── httpclient/ │ │ │ └── interceptor/ │ │ │ ├── HttpClient5AsyncForwardedInterceptorTest.java │ │ │ ├── HttpClient5AsyncTracingInterceptorTest.java │ │ │ ├── HttpClient5DoExecuteForwardedInterceptorTest.java │ │ │ ├── HttpClient5DoExecuteInterceptorTest.java │ │ │ ├── HttpClientDoExecuteForwardedInterceptorTest.java │ │ │ ├── HttpClientDoExecuteInterceptorTest.java │ │ │ └── TestConst.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── httpservlet/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── httpservlet/ │ │ │ ├── AccessPlugin.java │ │ │ ├── ForwardedPlugin.java │ │ │ ├── HttpServletPlugin.java │ │ │ ├── advice/ │ │ │ │ └── DoFilterPoints.java │ │ │ ├── interceptor/ │ │ │ │ ├── BaseServletInterceptor.java │ │ │ │ ├── DoFilterForwardedInterceptor.java │ │ │ │ ├── DoFilterMetricInterceptor.java │ │ │ │ ├── DoFilterTraceInterceptor.java │ │ │ │ ├── HttpServerRequest.java │ │ │ │ ├── ServletAccessLogServerInfo.java │ │ │ │ └── ServletHttpLogInterceptor.java │ │ │ └── utils/ │ │ │ ├── InternalAsyncListener.java │ │ │ └── ServletUtils.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── httpservlet/ │ │ │ └── interceptor/ │ │ │ ├── BaseServletInterceptorTest.java │ │ │ ├── DoFilterForwardedInterceptorTest.java │ │ │ ├── DoFilterMetricInterceptorTest.java │ │ │ ├── DoFilterTraceInterceptorTest.java │ │ │ ├── HttpServerRequestTest.java │ │ │ ├── ServletAccessLogInfoServerInfoTest.java │ │ │ ├── ServletHttpLogInterceptorTest.java │ │ │ ├── TestConst.java │ │ │ └── TestServletUtils.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── httpurlconnection/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── httpurlconnection/ │ │ │ ├── ForwardedPlugin.java │ │ │ ├── HttpURLConnectionPlugin.java │ │ │ ├── advice/ │ │ │ │ └── HttpURLConnectionGetResponseCodeAdvice.java │ │ │ └── interceptor/ │ │ │ ├── HttpURLConnectionGetResponseCodeForwardedInterceptor.java │ │ │ └── HttpURLConnectionGetResponseCodeInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── httpurlconnection/ │ │ │ └── interceptor/ │ │ │ ├── HttpURLConnectionGetResponseCodeForwardedInterceptorTest.java │ │ │ ├── HttpURLConnectionGetResponseCodeInterceptorTest.java │ │ │ └── TestUtils.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── httpurlconnection-jdk17/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── httpurlconnection/ │ │ │ └── jdk17/ │ │ │ ├── ForwardedPlugin.java │ │ │ ├── HttpURLConnectionPlugin.java │ │ │ ├── advice/ │ │ │ │ └── HttpURLConnectionAdvice.java │ │ │ └── interceptor/ │ │ │ ├── DynamicFieldUtils.java │ │ │ ├── HttpURLConnectionForwardedInterceptor.java │ │ │ ├── HttpURLConnectionInterceptor.java │ │ │ └── HttpURLConnectionUtils.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── httpurlconnection/ │ │ │ └── jdk17/ │ │ │ └── interceptor/ │ │ │ ├── HttpURLConnectionForwardedInterceptorTest.java │ │ │ ├── HttpURLConnectionInterceptorTest.java │ │ │ └── TestUtils.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── jdbc/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── jdbc/ │ │ │ ├── JdbcConnectionMetricPlugin.java │ │ │ ├── JdbcDataSourceMetricPlugin.java │ │ │ ├── JdbcRedirectPlugin.java │ │ │ ├── JdbcTracingPlugin.java │ │ │ ├── advice/ │ │ │ │ ├── HikariDataSourceAdvice.java │ │ │ │ ├── JdbcConnectionAdvice.java │ │ │ │ ├── JdbcDataSourceAdvice.java │ │ │ │ └── JdbcStatementAdvice.java │ │ │ ├── common/ │ │ │ │ ├── DatabaseInfo.java │ │ │ │ ├── JdbcUtils.java │ │ │ │ ├── MD5DictionaryItem.java │ │ │ │ ├── MD5ReportConsumer.java │ │ │ │ ├── MD5SQLCompression.java │ │ │ │ ├── SQLCompression.java │ │ │ │ ├── SQLCompressionFactory.java │ │ │ │ ├── SQLCompressionWrapper.java │ │ │ │ └── SqlInfo.java │ │ │ └── interceptor/ │ │ │ ├── JdbConPrepareOrCreateStmInterceptor.java │ │ │ ├── JdbcStmPrepareSqlInterceptor.java │ │ │ ├── metric/ │ │ │ │ ├── JdbcDataSourceMetricInterceptor.java │ │ │ │ ├── JdbcMetric.java │ │ │ │ └── JdbcStmMetricInterceptor.java │ │ │ ├── redirect/ │ │ │ │ └── HikariSetPropertyInterceptor.java │ │ │ └── tracing/ │ │ │ └── JdbcStmTracingInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── jdbc/ │ │ │ ├── MockJDBCStatement.java │ │ │ ├── TestUtils.java │ │ │ ├── common/ │ │ │ │ ├── DatabaseInfoTest.java │ │ │ │ ├── JdbcUtilsTest.java │ │ │ │ ├── MD5DictionaryItemTest.java │ │ │ │ ├── MD5ReportConsumerTest.java │ │ │ │ ├── MD5SQLCompressionTest.java │ │ │ │ └── SqlInfoTest.java │ │ │ └── interceptor/ │ │ │ ├── JdbConPrepareOrCreateStmInterceptorTest.java │ │ │ ├── JdbcStmPrepareSqlInterceptorTest.java │ │ │ ├── metric/ │ │ │ │ ├── JdbcDataSourceMetricInterceptorTest.java │ │ │ │ ├── JdbcMetricTest.java │ │ │ │ └── JdbcStmMetricInterceptorTest.java │ │ │ ├── redirect/ │ │ │ │ └── HikariSetPropertyInterceptorTest.java │ │ │ └── tracing/ │ │ │ └── JdbcStmTracingInterceptorTest.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── kafka/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── kafka/ │ │ │ ├── KafkaPlugin.java │ │ │ ├── KafkaRedirectPlugin.java │ │ │ ├── advice/ │ │ │ │ ├── KafkaConsumerAdvice.java │ │ │ │ ├── KafkaConsumerConfigAdvice.java │ │ │ │ ├── KafkaConsumerRecordAdvice.java │ │ │ │ ├── KafkaMessageListenerAdvice.java │ │ │ │ ├── KafkaProducerAdvice.java │ │ │ │ └── KafkaProducerConfigAdvice.java │ │ │ └── interceptor/ │ │ │ ├── AsyncCallback.java │ │ │ ├── KafkaUtils.java │ │ │ ├── initialize/ │ │ │ │ ├── ConsumerRecordInterceptor.java │ │ │ │ ├── KafkaConsumerConstructInterceptor.java │ │ │ │ ├── KafkaConsumerPollInterceptor.java │ │ │ │ └── KafkaProducerConstructInterceptor.java │ │ │ ├── metric/ │ │ │ │ ├── KafkaConsumerMetricInterceptor.java │ │ │ │ ├── KafkaMessageListenerMetricInterceptor.java │ │ │ │ ├── KafkaMetric.java │ │ │ │ ├── KafkaProducerMetricInterceptor.java │ │ │ │ └── MetricCallback.java │ │ │ ├── redirect/ │ │ │ │ ├── KafkaAbstractConfigConstructInterceptor.java │ │ │ │ ├── KafkaConsumerConfigConstructInterceptor.java │ │ │ │ └── KafkaProducerConfigConstructInterceptor.java │ │ │ └── tracing/ │ │ │ ├── KafkaConsumerRequest.java │ │ │ ├── KafkaConsumerTracingInterceptor.java │ │ │ ├── KafkaHeaders.java │ │ │ ├── KafkaMessageListenerTracingInterceptor.java │ │ │ ├── KafkaProducerDoSendInterceptor.java │ │ │ ├── KafkaProducerRequest.java │ │ │ ├── KafkaTags.java │ │ │ └── TraceCallback.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── kafka/ │ │ │ └── interceptor/ │ │ │ ├── AsyncCallbackTest.java │ │ │ ├── KafkaTestUtils.java │ │ │ ├── KafkaUtilsTest.java │ │ │ ├── MockConsumerRecord.java │ │ │ ├── MockKafkaConsumer.java │ │ │ ├── MockKafkaProducer.java │ │ │ ├── TestConst.java │ │ │ ├── initialize/ │ │ │ │ ├── ConsumerRecordInterceptorTest.java │ │ │ │ ├── KafkaConsumerConstructInterceptorTest.java │ │ │ │ ├── KafkaConsumerPollInterceptorTest.java │ │ │ │ ├── KafkaProducerConstructInterceptorTest.java │ │ │ │ └── MockDynamicFieldAccessor.java │ │ │ ├── metric/ │ │ │ │ ├── KafkaConsumerMetricInterceptorTest.java │ │ │ │ ├── KafkaMessageListenerMetricInterceptorTest.java │ │ │ │ ├── KafkaMetricTest.java │ │ │ │ ├── KafkaProducerMetricInterceptorTest.java │ │ │ │ └── MetricCallbackTest.java │ │ │ ├── redirect/ │ │ │ │ └── KafkaAbstractConfigConstructInterceptorTest.java │ │ │ └── tracing/ │ │ │ ├── KafkaConsumerRequestTest.java │ │ │ ├── KafkaConsumerTracingInterceptorTest.java │ │ │ ├── KafkaHeadersTest.java │ │ │ ├── KafkaMessageListenerTracingInterceptorTest.java │ │ │ ├── KafkaProducerDoSendInterceptorTest.java │ │ │ ├── KafkaProducerRequestTest.java │ │ │ └── TraceCallbackTest.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── log4j2-log-plugin/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── log4j2/ │ │ ├── Log4j2Plugin.java │ │ ├── interceptor/ │ │ │ └── Log4j2AppenderInterceptor.java │ │ ├── log/ │ │ │ └── Log4jLogMapper.java │ │ └── points/ │ │ └── AbstractLoggerPoints.java │ ├── logback/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── logback/ │ │ ├── LogbackPlugin.java │ │ ├── interceptor/ │ │ │ └── LogbackAppenderInterceptor.java │ │ ├── log/ │ │ │ └── LogbackLogMapper.java │ │ └── points/ │ │ └── LoggerPoints.java │ ├── mongodb/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── mongodb/ │ │ │ ├── MongoPlugin.java │ │ │ ├── MongoRedirectPlugin.java │ │ │ ├── MongoUtils.java │ │ │ ├── interceptor/ │ │ │ │ ├── InterceptorHelper.java │ │ │ │ ├── MetricHelper.java │ │ │ │ ├── MongoBaseInterceptor.java │ │ │ │ ├── MongoBaseMetricInterceptor.java │ │ │ │ ├── MongoBaseTraceInterceptor.java │ │ │ │ ├── MongoClientConstruct4MetricInterceptor.java │ │ │ │ ├── MongoClientConstruct4TraceInterceptor.java │ │ │ │ ├── MongoCtx.java │ │ │ │ ├── MongoDbRedirectInterceptor.java │ │ │ │ ├── MongoInternalConnectionSendAndReceiveAsync4MetricInterceptor.java │ │ │ │ ├── MongoInternalConnectionSendAndReceiveAsync4TraceInterceptor.java │ │ │ │ ├── MongoMetric.java │ │ │ │ ├── MongoReactiveInitMetricInterceptor.java │ │ │ │ ├── MongoReactiveInitTraceInterceptor.java │ │ │ │ ├── TraceHelper.java │ │ │ │ └── listener/ │ │ │ │ ├── MongoBaseCommandListener.java │ │ │ │ ├── MongoBaseMetricCommandListener.java │ │ │ │ ├── MongoBaseTraceCommandListener.java │ │ │ │ ├── MongoMetricCommandListener.java │ │ │ │ ├── MongoReactiveMetricCommandListener.java │ │ │ │ ├── MongoReactiveTraceCommandListener.java │ │ │ │ └── MongoTraceCommandListener.java │ │ │ └── points/ │ │ │ ├── MongoAsyncMongoClientsPoints.java │ │ │ ├── MongoClientImplPoints.java │ │ │ ├── MongoDBInternalConnectionPoints.java │ │ │ └── MongoRedirectPoints.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── plugin/ │ │ └── mongodb/ │ │ ├── MongoBaseTest.java │ │ ├── MongoMetricTest.java │ │ ├── MongoReactiveMetricTest.java │ │ ├── MongoReactiveTraceTest.java │ │ ├── MongoTraceTest.java │ │ └── TraceHelperTest.java │ ├── motan/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── motan/ │ │ │ ├── MotanPlugin.java │ │ │ ├── advice/ │ │ │ │ ├── MotanConsumerAdvice.java │ │ │ │ └── MotanProviderAdvice.java │ │ │ ├── config/ │ │ │ │ └── MotanPluginConfig.java │ │ │ └── interceptor/ │ │ │ ├── MotanClassUtils.java │ │ │ ├── MotanCtxUtils.java │ │ │ ├── metrics/ │ │ │ │ ├── MetricsFutureListener.java │ │ │ │ ├── MotanBaseMetricsInterceptor.java │ │ │ │ ├── MotanMetric.java │ │ │ │ ├── MotanMetricTags.java │ │ │ │ └── MotanMetricsInterceptor.java │ │ │ └── trace/ │ │ │ ├── MotanBaseInterceptor.java │ │ │ ├── MotanTags.java │ │ │ ├── consumer/ │ │ │ │ ├── MotanConsumerRequest.java │ │ │ │ ├── MotanConsumerTraceInterceptor.java │ │ │ │ └── TraceFutureListener.java │ │ │ └── provider/ │ │ │ ├── MotanProviderRequest.java │ │ │ └── MotanProviderTraceInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── motan/ │ │ │ └── interceptor/ │ │ │ ├── metrics/ │ │ │ │ └── MotanMetricsInterceptorTest.java │ │ │ └── trace/ │ │ │ ├── MotanInterceptorTest.java │ │ │ ├── consumer/ │ │ │ │ └── MotanConsumerInterceptorTest.java │ │ │ └── provider/ │ │ │ └── MotanProviderInterceptorTest.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── okhttp/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── okhttp/ │ │ │ ├── ForwardedPlugin.java │ │ │ ├── OkHttpPlugin.java │ │ │ ├── advice/ │ │ │ │ └── OkHttpAdvice.java │ │ │ └── interceptor/ │ │ │ ├── ForwardedRequest.java │ │ │ ├── InternalRequest.java │ │ │ ├── InternalResponse.java │ │ │ ├── OkHttpAsyncTracingInterceptor.java │ │ │ ├── OkHttpForwardedInterceptor.java │ │ │ └── OkHttpTracingInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── okhttp/ │ │ │ └── interceptor/ │ │ │ ├── OkHttpAsyncTracingInterceptorTest.java │ │ │ ├── OkHttpForwardedInterceptorTest.java │ │ │ ├── OkHttpTestUtils.java │ │ │ ├── OkHttpTracingInterceptorTest.java │ │ │ └── TestConst.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── pom.xml │ ├── rabbitmq/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── rabbitmq/ │ │ │ ├── RabbitMqConsumerMetric.java │ │ │ ├── RabbitMqPlugin.java │ │ │ ├── RabbitMqProducerMetric.java │ │ │ ├── RabbitMqRedirectPlugin.java │ │ │ ├── spring/ │ │ │ │ ├── RabbitMqMessageListenerAdvice.java │ │ │ │ └── interceptor/ │ │ │ │ ├── RabbitMqMessageListenerOnMessageInterceptor.java │ │ │ │ ├── RabbitMqOnMessageMetricInterceptor.java │ │ │ │ └── RabbitMqOnMessageTracingInterceptor.java │ │ │ └── v5/ │ │ │ ├── advice/ │ │ │ │ ├── RabbitMqChannelAdvice.java │ │ │ │ ├── RabbitMqConfigFactoryAdvice.java │ │ │ │ ├── RabbitMqConsumerAdvice.java │ │ │ │ └── RabbitMqPropertyAdvice.java │ │ │ └── interceptor/ │ │ │ ├── RabbitMqChannelConsumeInterceptor.java │ │ │ ├── RabbitMqChannelConsumerDeliveryInterceptor.java │ │ │ ├── RabbitMqChannelPublishInterceptor.java │ │ │ ├── RabbitMqConsumerHandleDeliveryInterceptor.java │ │ │ ├── metirc/ │ │ │ │ ├── RabbitMqConsumerMetricInterceptor.java │ │ │ │ └── RabbitMqProducerMetricInterceptor.java │ │ │ ├── redirect/ │ │ │ │ ├── RabbitMqConfigFactoryInterceptor.java │ │ │ │ └── RabbitMqPropertyInterceptor.java │ │ │ └── tracing/ │ │ │ ├── RabbitMqChannelPublishTracingInterceptor.java │ │ │ └── RabbitMqConsumerTracingInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── rabbitmq/ │ │ │ ├── RabbitMqConsumerMetricTest.java │ │ │ ├── RabbitMqProducerMetricTest.java │ │ │ ├── TestUtils.java │ │ │ ├── spring/ │ │ │ │ └── interceptor/ │ │ │ │ ├── RabbitMqMessageListenerOnMessageInterceptorTest.java │ │ │ │ ├── RabbitMqOnMessageMetricInterceptorTest.java │ │ │ │ └── RabbitMqOnMessageTracingInterceptorTest.java │ │ │ └── v5/ │ │ │ └── interceptor/ │ │ │ ├── MockConsumer.java │ │ │ ├── RabbitMqChannelConsumeInterceptorTest.java │ │ │ ├── RabbitMqChannelConsumerDeliveryInterceptorTest.java │ │ │ ├── RabbitMqChannelPublishInterceptorTest.java │ │ │ ├── RabbitMqConsumerHandleDeliveryInterceptorTest.java │ │ │ ├── metirc/ │ │ │ │ ├── RabbitMqConsumerMetricInterceptorTest.java │ │ │ │ └── RabbitMqProducerMetricInterceptorTest.java │ │ │ ├── redirect/ │ │ │ │ └── RabbitMqPropertyInterceptorTest.java │ │ │ └── tracing/ │ │ │ ├── RabbitMqChannelPublishTracingInterceptorTest.java │ │ │ └── RabbitMqConsumerTracingInterceptorTest.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── redis/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── redis/ │ │ │ ├── RedisPlugin.java │ │ │ ├── RedisRedirectPlugin.java │ │ │ ├── advice/ │ │ │ │ ├── JedisAdvice.java │ │ │ │ ├── JedisConstructorAdvice.java │ │ │ │ ├── LettuceRedisClientAdvice.java │ │ │ │ ├── RedisChannelWriterAdvice.java │ │ │ │ ├── RedisClusterClientAdvice.java │ │ │ │ ├── RedisPropertiesAdvice.java │ │ │ │ ├── RedisPropertiesClusterAdvice.java │ │ │ │ └── StatefulRedisConnectionAdvice.java │ │ │ └── interceptor/ │ │ │ ├── RedisClassUtils.java │ │ │ ├── RedisClientUtils.java │ │ │ ├── initialize/ │ │ │ │ ├── CommonRedisClientInterceptor.java │ │ │ │ ├── CompletableFutureWrapper.java │ │ │ │ ├── ConnectionFutureWrapper.java │ │ │ │ ├── RedisClientInterceptor.java │ │ │ │ └── RedisClusterClientInterceptor.java │ │ │ ├── metric/ │ │ │ │ ├── CommonRedisMetricInterceptor.java │ │ │ │ ├── JedisMetricInterceptor.java │ │ │ │ └── LettuceMetricInterceptor.java │ │ │ ├── redirect/ │ │ │ │ ├── JedisConstructorInterceptor.java │ │ │ │ ├── LettuceRedisClientConstructInterceptor.java │ │ │ │ ├── RedisPropertiesClusterSetNodesInterceptor.java │ │ │ │ └── RedisPropertiesSetPropertyInterceptor.java │ │ │ └── tracing/ │ │ │ ├── CommonRedisTracingInterceptor.java │ │ │ ├── JedisTracingInterceptor.java │ │ │ ├── LettuceTracingInterceptor.java │ │ │ └── StatefulRedisConnectionInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── redis/ │ │ │ └── interceptor/ │ │ │ ├── RedisUtils.java │ │ │ ├── TestConst.java │ │ │ ├── initialize/ │ │ │ │ ├── CommonRedisClientInterceptorTest.java │ │ │ │ ├── CompletableFutureWrapperTest.java │ │ │ │ ├── ConnectionFutureWrapperTest.java │ │ │ │ └── DynamicFieldAccessorObj.java │ │ │ ├── metric/ │ │ │ │ ├── CommonRedisMetricInterceptorTest.java │ │ │ │ ├── JedisMetricInterceptorTest.java │ │ │ │ └── LettuceMetricInterceptorTest.java │ │ │ ├── redirect/ │ │ │ │ ├── JedisConstructorInterceptorTest.java │ │ │ │ ├── LettuceRedisClientConstructInterceptorTest.java │ │ │ │ ├── RedisPropertiesClusterSetNodesInterceptorTest.java │ │ │ │ └── RedisPropertiesSetPropertyInterceptorTest.java │ │ │ └── tracing/ │ │ │ ├── CommonRedisTracingInterceptorTest.java │ │ │ ├── JedisTracingInterceptorTest.java │ │ │ └── LettuceTracingInterceptorTest.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── servicename/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── servicename/ │ │ │ ├── Const.java │ │ │ ├── ReflectionTool.java │ │ │ ├── ServiceNamePlugin.java │ │ │ ├── ServiceNamePluginConfig.java │ │ │ ├── advice/ │ │ │ │ ├── FeignBlockingLoadBalancerClientAdvice.java │ │ │ │ ├── FeignLoadBalancerAdvice.java │ │ │ │ ├── FilteringWebHandlerAdvice.java │ │ │ │ ├── LoadBalancerFeignClientAdvice.java │ │ │ │ ├── RestTemplateInterceptAdvice.java │ │ │ │ └── WebClientFilterAdvice.java │ │ │ └── interceptor/ │ │ │ ├── BaseServiceNameInterceptor.java │ │ │ ├── FeignBlockingLoadBalancerClientInterceptor.java │ │ │ ├── FeignLoadBalancerInterceptor.java │ │ │ ├── FilteringWebHandlerInterceptor.java │ │ │ ├── LoadBalancerFeignClientInterceptor.java │ │ │ ├── RestTemplateInterceptInterceptor.java │ │ │ └── WebClientFilterInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ ├── com/ │ │ │ │ └── megaease/ │ │ │ │ └── easeagent/ │ │ │ │ └── plugin/ │ │ │ │ └── servicename/ │ │ │ │ ├── ReflectionToolTest.java │ │ │ │ └── interceptor/ │ │ │ │ ├── BaseServiceNameInterceptorTest.java │ │ │ │ ├── CheckUtils.java │ │ │ │ ├── FeignBlockingLoadBalancerClientInterceptorTest.java │ │ │ │ ├── FeignLoadBalancerInterceptorTest.java │ │ │ │ ├── FilteringWebHandlerInterceptorTest.java │ │ │ │ ├── LoadBalancerFeignClientInterceptorTest.java │ │ │ │ ├── RestTemplateInterceptInterceptorTest.java │ │ │ │ ├── TestConst.java │ │ │ │ └── WebClientFilterInterceptorTest.java │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ ├── cloud/ │ │ │ │ └── openfeign/ │ │ │ │ └── ribbon/ │ │ │ │ └── MockRibbonRequest.java │ │ │ └── web/ │ │ │ └── reactive/ │ │ │ └── function/ │ │ │ └── client/ │ │ │ └── MockClientRequest.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── sofarpc/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── sofarpc/ │ │ │ ├── SofaRpcCtxUtils.java │ │ │ ├── SofaRpcMetricsTags.java │ │ │ ├── SofaRpcPlugin.java │ │ │ ├── SofaRpcTraceTags.java │ │ │ ├── adivce/ │ │ │ │ ├── BoltFutureInvokeCallbackConstructAdvice.java │ │ │ │ ├── ConsumerAdvice.java │ │ │ │ ├── FutureInvokeCallbackConstructAdvice.java │ │ │ │ ├── ProviderAdvice.java │ │ │ │ ├── ResponseCallbackAdvice.java │ │ │ │ └── ResponseFutureAdvice.java │ │ │ ├── config/ │ │ │ │ └── SofaRpcTraceConfig.java │ │ │ └── interceptor/ │ │ │ ├── initalize/ │ │ │ │ └── SofaRpcFutureInvokeCallbackConstructInterceptor.java │ │ │ ├── metrics/ │ │ │ │ ├── SofaRpcMetrics.java │ │ │ │ ├── SofaRpcMetricsBaseInterceptor.java │ │ │ │ ├── callback/ │ │ │ │ │ ├── SofaRpcResponseCallbackMetrics.java │ │ │ │ │ └── SofaRpcResponseCallbackMetricsInterceptor.java │ │ │ │ ├── common/ │ │ │ │ │ └── SofaRpcMetricsInterceptor.java │ │ │ │ └── future/ │ │ │ │ └── SofaRpcResponseFutureMetricsInterceptor.java │ │ │ └── trace/ │ │ │ ├── SofaRpcTraceBaseInterceptor.java │ │ │ ├── callback/ │ │ │ │ ├── SofaRpcResponseCallbackTrace.java │ │ │ │ └── SofaRpcResponseCallbackTraceInterceptor.java │ │ │ ├── common/ │ │ │ │ ├── SofaClientTraceRequest.java │ │ │ │ ├── SofaRpcConsumerTraceInterceptor.java │ │ │ │ ├── SofaRpcProviderTraceInterceptor.java │ │ │ │ └── SofaServerTraceRequest.java │ │ │ └── future/ │ │ │ └── SofaRpcResponseFutureTraceInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── sofarpc/ │ │ │ └── interceptor/ │ │ │ ├── BaseInterceptorTest.java │ │ │ ├── MockBoltResponseFuture.java │ │ │ ├── metrics/ │ │ │ │ ├── BaseMetricsInterceptorTest.java │ │ │ │ ├── callback/ │ │ │ │ │ ├── MockSofaResponseCallback.java │ │ │ │ │ └── SofaRpcResponseCallbackMetricsInterceptorTest.java │ │ │ │ ├── common/ │ │ │ │ │ └── SofaRpcMetricsInterceptorTest.java │ │ │ │ └── future/ │ │ │ │ └── SofaRpcResponseFutureMetricsInterceptorTest.java │ │ │ └── trace/ │ │ │ ├── callback/ │ │ │ │ ├── MockSofaResponseCallback.java │ │ │ │ └── SofaRpcResponseCallbackTraceInterceptorTest.java │ │ │ ├── common/ │ │ │ │ ├── SofaRpcConsumerTraceInterceptorTest.java │ │ │ │ └── SofaRpcProviderTraceInterceptorTest.java │ │ │ └── future/ │ │ │ └── SofaRpcResponseFutureTraceInterceptorTest.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── spring-boot-3.5.3/ │ │ ├── pom.xml │ │ ├── spring-boot-gateway-3.5.3/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── easeagent/ │ │ │ │ └── plugin/ │ │ │ │ └── spring353/ │ │ │ │ └── gateway/ │ │ │ │ ├── AccessPlugin.java │ │ │ │ ├── ForwardedPlugin.java │ │ │ │ ├── GatewayCons.java │ │ │ │ ├── SpringGatewayPlugin.java │ │ │ │ ├── advice/ │ │ │ │ │ ├── AgentGlobalFilterAdvice.java │ │ │ │ │ ├── HttpHeadersFilterAdvice.java │ │ │ │ │ └── InitGlobalFilterAdvice.java │ │ │ │ ├── interceptor/ │ │ │ │ │ ├── TimeUtils.java │ │ │ │ │ ├── forwarded/ │ │ │ │ │ │ └── GatewayServerForwardedInterceptor.java │ │ │ │ │ ├── initialize/ │ │ │ │ │ │ ├── AgentGlobalFilter.java │ │ │ │ │ │ └── GlobalFilterInterceptor.java │ │ │ │ │ ├── log/ │ │ │ │ │ │ ├── GatewayAccessLogInterceptor.java │ │ │ │ │ │ └── SpringGatewayAccessLogServerInfo.java │ │ │ │ │ ├── metric/ │ │ │ │ │ │ └── GatewayMetricsInterceptor.java │ │ │ │ │ └── tracing/ │ │ │ │ │ ├── FluxHttpServerRequest.java │ │ │ │ │ ├── FluxHttpServerResponse.java │ │ │ │ │ ├── GatewayServerTracingInterceptor.java │ │ │ │ │ └── HttpHeadersFilterTracingInterceptor.java │ │ │ │ └── reactor/ │ │ │ │ ├── AgentCoreSubscriber.java │ │ │ │ └── AgentMono.java │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── easeagent/ │ │ │ │ └── plugin/ │ │ │ │ └── spring353/ │ │ │ │ └── gateway/ │ │ │ │ ├── TestConst.java │ │ │ │ ├── TestServerWebExchangeUtils.java │ │ │ │ ├── interceptor/ │ │ │ │ │ ├── TimeUtilsTest.java │ │ │ │ │ ├── forwarded/ │ │ │ │ │ │ └── GatewayServerForwardedInterceptorTest.java │ │ │ │ │ ├── initialize/ │ │ │ │ │ │ ├── AgentGlobalFilterTest.java │ │ │ │ │ │ └── GlobalFilterInterceptorTest.java │ │ │ │ │ ├── log/ │ │ │ │ │ │ ├── GatewayAccessLogInfoInterceptorTest.java │ │ │ │ │ │ └── SpringGatewayAccessLogInfoServerInfoTest.java │ │ │ │ │ ├── metric/ │ │ │ │ │ │ ├── GatewayMetricsInterceptorTest.java │ │ │ │ │ │ └── MockRouteBuilder.java │ │ │ │ │ └── tracing/ │ │ │ │ │ ├── FluxHttpServerRequestTest.java │ │ │ │ │ ├── FluxHttpServerResponseTest.java │ │ │ │ │ ├── GatewayServerTracingInterceptorTest.java │ │ │ │ │ └── HttpHeadersFilterTracingInterceptorTest.java │ │ │ │ └── reactor/ │ │ │ │ ├── AgentCoreSubscriberTest.java │ │ │ │ ├── AgentMonoTest.java │ │ │ │ └── MockCoreSubscriber.java │ │ │ └── resources/ │ │ │ └── mock_agent.properties │ │ ├── spring-boot-rest-template-3.5.3/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── megaease/ │ │ │ │ └── easeagent/ │ │ │ │ └── plugin/ │ │ │ │ └── rest/ │ │ │ │ └── template/ │ │ │ │ ├── ForwardedPlugin.java │ │ │ │ ├── RestTemplatePlugin.java │ │ │ │ ├── advice/ │ │ │ │ │ └── ClientHttpRequestAdvice.java │ │ │ │ └── interceptor/ │ │ │ │ ├── forwarded/ │ │ │ │ │ └── RestTemplateForwardedInterceptor.java │ │ │ │ └── tracing/ │ │ │ │ └── ClientHttpRequestInterceptor.java │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ ├── com/ │ │ │ │ │ └── megaease/ │ │ │ │ │ └── easeagent/ │ │ │ │ │ └── plugin/ │ │ │ │ │ └── rest/ │ │ │ │ │ └── template/ │ │ │ │ │ └── interceptor/ │ │ │ │ │ ├── TestConst.java │ │ │ │ │ ├── forwarded/ │ │ │ │ │ │ └── RestTemplateForwardedInterceptorTest.java │ │ │ │ │ └── tracing/ │ │ │ │ │ └── ClientHttpRequestInterceptorTest.java │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── http/ │ │ │ │ └── client/ │ │ │ │ └── SimpleClientHttpResponseFactory.java │ │ │ └── resources/ │ │ │ └── mock_agent.properties │ │ └── spring-boot-servicename-3.5.3/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── servicename/ │ │ │ └── springboot353/ │ │ │ ├── Const.java │ │ │ ├── ReflectionTool.java │ │ │ ├── ServiceNamePlugin.java │ │ │ ├── ServiceNamePluginConfig.java │ │ │ ├── advice/ │ │ │ │ ├── FeignClientLoadBalancerClientAdvice.java │ │ │ │ ├── FilteringWebHandlerAdvice.java │ │ │ │ ├── RestTemplateInterceptAdvice.java │ │ │ │ └── WebClientFilterAdvice.java │ │ │ └── interceptor/ │ │ │ ├── BaseServiceNameInterceptor.java │ │ │ ├── FeignClientLoadBalancerClientInterceptor.java │ │ │ ├── FilteringWebHandlerInterceptor.java │ │ │ ├── RestTemplateInterceptInterceptor.java │ │ │ └── WebClientFilterInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── servicename/ │ │ │ └── springboot353/ │ │ │ ├── ReflectionToolTest.java │ │ │ └── interceptor/ │ │ │ ├── BaseServiceNameInterceptorTest.java │ │ │ ├── CheckUtils.java │ │ │ ├── FeignBlockingLoadBalancerClientInterceptorTest.java │ │ │ ├── FilteringWebHandlerInterceptorTest.java │ │ │ ├── RestTemplateInterceptInterceptorTest.java │ │ │ ├── TestConst.java │ │ │ └── WebClientFilterInterceptorTest.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── spring-gateway/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── spring/ │ │ │ └── gateway/ │ │ │ ├── AccessPlugin.java │ │ │ ├── ForwardedPlugin.java │ │ │ ├── SpringGatewayPlugin.java │ │ │ ├── advice/ │ │ │ │ ├── AgentGlobalFilterAdvice.java │ │ │ │ ├── CodeCons.java │ │ │ │ ├── HttpHeadersFilterAdvice.java │ │ │ │ └── InitGlobalFilterAdvice.java │ │ │ ├── interceptor/ │ │ │ │ ├── GatewayCons.java │ │ │ │ ├── initialize/ │ │ │ │ │ ├── AgentGlobalFilter.java │ │ │ │ │ ├── GatewayServerForwardedInterceptor.java │ │ │ │ │ └── GlobalFilterInterceptor.java │ │ │ │ ├── metric/ │ │ │ │ │ ├── GatewayMetricsInterceptor.java │ │ │ │ │ ├── TimeUtils.java │ │ │ │ │ └── log/ │ │ │ │ │ ├── GatewayAccessLogInterceptor.java │ │ │ │ │ └── SpringGatewayAccessLogServerInfo.java │ │ │ │ └── tracing/ │ │ │ │ ├── FluxHttpServerRequest.java │ │ │ │ ├── FluxHttpServerResponse.java │ │ │ │ ├── GatewayServerTracingInterceptor.java │ │ │ │ └── HttpHeadersFilterTracingInterceptor.java │ │ │ └── reactor/ │ │ │ ├── AgentCoreSubscriber.java │ │ │ └── AgentMono.java │ │ └── test/ │ │ ├── java/ │ │ │ └── easeagent/ │ │ │ └── plugin/ │ │ │ └── spring/ │ │ │ └── gateway/ │ │ │ ├── TestConst.java │ │ │ ├── TestServerWebExchangeUtils.java │ │ │ ├── interceptor/ │ │ │ │ ├── initialize/ │ │ │ │ │ ├── AgentGlobalFilterTest.java │ │ │ │ │ ├── GatewayServerForwardedInterceptorTest.java │ │ │ │ │ └── GlobalFilterInterceptorTest.java │ │ │ │ ├── metric/ │ │ │ │ │ ├── GatewayMetricsInterceptorTest.java │ │ │ │ │ ├── MockRouteBuilder.java │ │ │ │ │ ├── TimeUtilsTest.java │ │ │ │ │ └── log/ │ │ │ │ │ ├── GatewayAccessLogInfoInterceptorTest.java │ │ │ │ │ └── SpringGatewayAccessLogInfoServerInfoTest.java │ │ │ │ └── tracing/ │ │ │ │ ├── FluxHttpServerRequestTest.java │ │ │ │ ├── FluxHttpServerResponseTest.java │ │ │ │ ├── GatewayServerTracingInterceptorTest.java │ │ │ │ └── HttpHeadersFilterTracingInterceptorTest.java │ │ │ └── reactor/ │ │ │ ├── AgentCoreSubscriberTest.java │ │ │ ├── AgentMonoTest.java │ │ │ └── MockCoreSubscriber.java │ │ └── resources/ │ │ └── mock_agent.properties │ ├── springweb/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── megaease/ │ │ │ │ ├── easeagent/ │ │ │ │ │ └── plugin/ │ │ │ │ │ └── springweb/ │ │ │ │ │ ├── FeignClientPlugin.java │ │ │ │ │ ├── ForwardedPlugin.java │ │ │ │ │ ├── RestTemplatePlugin.java │ │ │ │ │ ├── SpringWebPlugin.java │ │ │ │ │ ├── WebClientPlugin.java │ │ │ │ │ ├── advice/ │ │ │ │ │ │ ├── ClientHttpRequestAdvice.java │ │ │ │ │ │ ├── FeignClientAdvice.java │ │ │ │ │ │ ├── WebClientBuilderAdvice.java │ │ │ │ │ │ └── WebClientFilterAdvice.java │ │ │ │ │ ├── interceptor/ │ │ │ │ │ │ ├── HeadersFieldFinder.java │ │ │ │ │ │ ├── forwarded/ │ │ │ │ │ │ │ ├── FeignClientForwardedInterceptor.java │ │ │ │ │ │ │ ├── RestTemplateForwardedInterceptor.java │ │ │ │ │ │ │ └── WebClientFilterForwardedInterceptor.java │ │ │ │ │ │ ├── initialize/ │ │ │ │ │ │ │ └── WebClientBuildInterceptor.java │ │ │ │ │ │ └── tracing/ │ │ │ │ │ │ ├── ClientHttpRequestInterceptor.java │ │ │ │ │ │ ├── FeignClientTracingInterceptor.java │ │ │ │ │ │ └── WebClientFilterTracingInterceptor.java │ │ │ │ │ └── reactor/ │ │ │ │ │ ├── AgentCoreSubscriber.java │ │ │ │ │ └── AgentMono.java │ │ │ │ └── plugin/ │ │ │ │ └── easeagent/ │ │ │ │ └── springweb/ │ │ │ │ └── interceptor/ │ │ │ │ └── tracing/ │ │ │ │ └── WebClientTracingFilter.java │ │ │ └── resources/ │ │ │ └── application.yaml │ │ └── test/ │ │ ├── java/ │ │ │ ├── com/ │ │ │ │ └── megaease/ │ │ │ │ └── easeagent/ │ │ │ │ └── plugin/ │ │ │ │ └── springweb/ │ │ │ │ ├── interceptor/ │ │ │ │ │ ├── HeadersFieldFinderTest.java │ │ │ │ │ ├── RequestUtils.java │ │ │ │ │ ├── TestConst.java │ │ │ │ │ ├── forwarded/ │ │ │ │ │ │ ├── FeignClientForwardedInterceptorTest.java │ │ │ │ │ │ ├── RestTemplateForwardedInterceptorTest.java │ │ │ │ │ │ └── WebClientFilterForwardedInterceptorTest.java │ │ │ │ │ ├── initialize/ │ │ │ │ │ │ └── WebClientBuildInterceptorTest.java │ │ │ │ │ └── tracing/ │ │ │ │ │ ├── ClientHttpRequestInterceptorTest.java │ │ │ │ │ ├── FeignClientTracingInterceptorTest.java │ │ │ │ │ └── WebClientFilterTracingInterceptorTest.java │ │ │ │ └── reactor/ │ │ │ │ ├── AgentCoreSubscriberTest.java │ │ │ │ ├── AgentMonoTest.java │ │ │ │ ├── MockCoreSubscriber.java │ │ │ │ └── MockMono.java │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ ├── http/ │ │ │ │ └── client/ │ │ │ │ └── SimpleClientHttpResponseFactory.java │ │ │ └── web/ │ │ │ └── reactive/ │ │ │ └── function/ │ │ │ └── client/ │ │ │ ├── MockClientRequest.java │ │ │ └── MockDefaultClientResponse.java │ │ └── resources/ │ │ └── mock_agent.properties │ └── tomcat-jdk17/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── plugin/ │ │ └── tomcat/ │ │ ├── AccessPlugin.java │ │ ├── ForwardedPlugin.java │ │ ├── TomcatPlugin.java │ │ ├── advice/ │ │ │ └── FilterChainPoints.java │ │ ├── interceptor/ │ │ │ ├── BaseServletInterceptor.java │ │ │ ├── FilterChainForwardedInterceptor.java │ │ │ ├── FilterChainMetricInterceptor.java │ │ │ ├── FilterChainTraceInterceptor.java │ │ │ ├── HttpServerRequest.java │ │ │ ├── TomcatAccessLogServerInfo.java │ │ │ └── TomcatHttpLogInterceptor.java │ │ └── utils/ │ │ ├── InternalAsyncListener.java │ │ └── ServletUtils.java │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── plugin/ │ │ └── tomcat/ │ │ └── interceptor/ │ │ ├── BaseServletInterceptorTest.java │ │ ├── FilterChainForwardedInterceptorTest.java │ │ ├── FilterChainMetricInterceptorTest.java │ │ ├── FilterChainTraceInterceptorTest.java │ │ ├── HttpServerRequestTest.java │ │ ├── ServletAccessLogInfoServerInfoTest.java │ │ ├── TestConst.java │ │ ├── TestServletUtils.java │ │ └── TomcatHttpLogInterceptorTest.java │ └── resources/ │ └── mock_agent.properties ├── pom.xml ├── release ├── report/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ ├── com/ │ │ │ └── megaease/ │ │ │ └── easeagent/ │ │ │ └── report/ │ │ │ ├── AgentReportAware.java │ │ │ ├── DefaultAgentReport.java │ │ │ ├── GlobalExtractor.java │ │ │ ├── OutputProperties.java │ │ │ ├── ReportConfigChange.java │ │ │ ├── async/ │ │ │ │ ├── AsyncProps.java │ │ │ │ ├── AsyncReporter.java │ │ │ │ ├── AsyncReporterMetrics.java │ │ │ │ ├── DefaultAsyncReporter.java │ │ │ │ ├── log/ │ │ │ │ │ ├── AccessLogReporter.java │ │ │ │ │ ├── ApplicationLogReporter.java │ │ │ │ │ └── LogAsyncProps.java │ │ │ │ ├── trace/ │ │ │ │ │ ├── SDKAsyncReporter.java │ │ │ │ │ └── TraceAsyncProps.java │ │ │ │ └── zipkin/ │ │ │ │ ├── AgentBufferNextMessage.java │ │ │ │ ├── AgentByteBoundedQueue.java │ │ │ │ └── WithSizeConsumer.java │ │ │ ├── encoder/ │ │ │ │ ├── PackedMessage.java │ │ │ │ ├── log/ │ │ │ │ │ ├── AccessLogJsonEncoder.java │ │ │ │ │ ├── AccessLogWriter.java │ │ │ │ │ ├── LogDataJsonEncoder.java │ │ │ │ │ ├── LogDataWriter.java │ │ │ │ │ └── pattern/ │ │ │ │ │ ├── LogDataDatePatternConverterDelegate.java │ │ │ │ │ ├── LogDataLevelPatternConverter.java │ │ │ │ │ ├── LogDataLineSeparatorPatternConverter.java │ │ │ │ │ ├── LogDataLoggerPatternConverter.java │ │ │ │ │ ├── LogDataMdcPatternConverter.java │ │ │ │ │ ├── LogDataPatternConverter.java │ │ │ │ │ ├── LogDataPatternFormatter.java │ │ │ │ │ ├── LogDataSimpleLiteralPatternConverter.java │ │ │ │ │ ├── LogDataThreadNamePatternConverter.java │ │ │ │ │ ├── LogDataThrowablePatternConverter.java │ │ │ │ │ ├── NamePatternConverter.java │ │ │ │ │ ├── NoOpPatternConverter.java │ │ │ │ │ └── SimpleMessageConverter.java │ │ │ │ ├── metric/ │ │ │ │ │ └── MetricJsonEncoder.java │ │ │ │ └── span/ │ │ │ │ ├── AbstractAgentV2SpanEndpointWriter.java │ │ │ │ ├── AgentV2SpanAnnotationsWriter.java │ │ │ │ ├── AgentV2SpanBaseWriter.java │ │ │ │ ├── AgentV2SpanGlobalWriter.java │ │ │ │ ├── AgentV2SpanLocalEndpointWriter.java │ │ │ │ ├── AgentV2SpanRemoteEndpointWriter.java │ │ │ │ ├── AgentV2SpanTagsWriter.java │ │ │ │ ├── AgentV2SpanWriter.java │ │ │ │ ├── GlobalExtrasSupplier.java │ │ │ │ ├── SpanJsonEncoder.java │ │ │ │ └── okhttp/ │ │ │ │ ├── HttpSpanJsonEncoder.java │ │ │ │ └── OkHttpJsonRequestBody.java │ │ │ ├── metric/ │ │ │ │ ├── MetricItem.java │ │ │ │ ├── MetricProps.java │ │ │ │ └── MetricReporterFactoryImpl.java │ │ │ ├── plugin/ │ │ │ │ ├── NoOpCall.java │ │ │ │ ├── NoOpEncoder.java │ │ │ │ ├── ReporterLoader.java │ │ │ │ └── ReporterRegistry.java │ │ │ ├── sender/ │ │ │ │ ├── AgentKafkaSender.java │ │ │ │ ├── AgentLoggerSender.java │ │ │ │ ├── NoOpSender.java │ │ │ │ ├── SenderConfigDecorator.java │ │ │ │ ├── SenderWithEncoder.java │ │ │ │ ├── ZipkinCallWrapper.java │ │ │ │ ├── metric/ │ │ │ │ │ ├── KeySender.java │ │ │ │ │ ├── MetricKafkaSender.java │ │ │ │ │ └── log4j/ │ │ │ │ │ ├── AppenderManager.java │ │ │ │ │ ├── LoggerFactory.java │ │ │ │ │ ├── MetricRefreshableAppender.java │ │ │ │ │ ├── RefreshableAppender.java │ │ │ │ │ └── TestableAppender.java │ │ │ │ └── okhttp/ │ │ │ │ ├── ByteRequestBody.java │ │ │ │ ├── HttpCall.java │ │ │ │ └── HttpSender.java │ │ │ ├── trace/ │ │ │ │ ├── Platform.java │ │ │ │ ├── RefreshableReporter.java │ │ │ │ ├── ReportSpanBuilder.java │ │ │ │ └── TraceReport.java │ │ │ └── util/ │ │ │ ├── SpanUtils.java │ │ │ ├── TextUtils.java │ │ │ └── Utils.java │ │ └── zipkin2/ │ │ └── reporter/ │ │ ├── TracerConverter.java │ │ ├── brave/ │ │ │ ├── ConvertSpanReporter.java │ │ │ └── ConvertZipkinSpanHandler.java │ │ └── kafka11/ │ │ ├── SDKKafkaSender.java │ │ ├── SDKSender.java │ │ └── SimpleSender.java │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── report/ │ │ ├── HttpSenderTest.java │ │ ├── async/ │ │ │ └── zipkin/ │ │ │ └── AgentByteBoundedQueueTest.java │ │ ├── encoder/ │ │ │ └── log/ │ │ │ ├── LogDataJsonEncoderTest.java │ │ │ └── LogDataWriterTest.java │ │ ├── metric/ │ │ │ ├── MetricPropsTest.java │ │ │ └── MetricReporterFactoryTest.java │ │ ├── sender/ │ │ │ ├── AgentKafkaSenderTest.java │ │ │ └── metric/ │ │ │ └── log4j/ │ │ │ └── AppenderManagerTest.java │ │ ├── trace/ │ │ │ └── TraceReportTest.java │ │ └── utils/ │ │ └── UtilsTest.java │ └── resources/ │ └── sender/ │ ├── outputServer_disabled.json │ ├── outputServer_empty_bootstrapServer_disabled.json │ ├── outputServer_empty_bootstrapServer_enabled.json │ └── outputServer_enabled.json ├── resources/ │ ├── rootfs/ │ │ └── Dockerfile │ └── scripts/ │ ├── Jenkinsfile │ └── build-image.sh └── zipkin/ ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── megaease/ │ │ └── easeagent/ │ │ └── zipkin/ │ │ ├── CustomTagsSpanHandler.java │ │ ├── TracingProviderImpl.java │ │ ├── impl/ │ │ │ ├── AsyncRequest.java │ │ │ ├── MessageImpl.java │ │ │ ├── RemoteGetterImpl.java │ │ │ ├── RemoteSetterImpl.java │ │ │ ├── RequestContextImpl.java │ │ │ ├── ScopeImpl.java │ │ │ ├── SpanContextImpl.java │ │ │ ├── SpanImpl.java │ │ │ ├── TracingImpl.java │ │ │ └── message/ │ │ │ ├── MessagingTracingImpl.java │ │ │ ├── ZipkinConsumerRequest.java │ │ │ └── ZipkinProducerRequest.java │ │ └── logging/ │ │ ├── AgentLogMDC.java │ │ ├── AgentMDCScopeDecorator.java │ │ └── LogUtils.java │ └── resources/ │ └── META-INF/ │ └── services/ │ └── com.megaease.easeagent.plugin.bean.BeanProvider └── test/ └── java/ ├── brave/ │ ├── TracerTestUtils.java │ └── internal/ │ └── collect/ │ └── WeakConcurrentMapTestUtils.java └── com/ └── megaease/ └── easeagent/ └── zipkin/ ├── CustomTagsSpanHandlerTest.java ├── TracingProviderImplMock.java ├── TracingProviderImplTest.java ├── impl/ │ ├── AsyncRequestTest.java │ ├── MessageImplTest.java │ ├── MessagingRequestMock.java │ ├── RemoteGetterImplTest.java │ ├── RemoteSetterImplTest.java │ ├── RequestContextImplTest.java │ ├── RequestMock.java │ ├── ScopeImplTest.java │ ├── SpanContextImplTest.java │ ├── SpanImplTest.java │ ├── TracingImplTest.java │ └── message/ │ ├── MessagingTracingImplTest.java │ ├── ZipkinConsumerRequestTest.java │ └── ZipkinProducerRequestTest.java └── logging/ ├── AgentLogMDCTest.java ├── AgentMDCScopeDecoratorTest.java └── LogUtilsTest.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf [*.md] trim_trailing_whitespace = false ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- --- name: 🐞 Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Execute '...' 2. Send a request to '....' 3. See error **Expected behavior** A clear and concise description of what you expected to happen. **Version** The version number of Easegress. **Configuration** * EaseAgent Configuration ``` ``` **Logs** ``` EaseAgent logs, if applicable. ``` **OS and Hardware** - OS: [e.g. Ubuntu 20.04] - CPU:[e.g. Intel(R) Core(TM) i5-8265U] - Memory: [e.g. 16GB] **Additional context** Add any other context about the problem here. --- Thanks for contributing 🎉! ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- --- name: 🚀 Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. --- Thanks for contributing 🎉! ================================================ FILE: .github/workflows/build.yml ================================================ # This is a basic workflow to help you get started with Actions name: Build & Test # Controls when the workflow will run on: # Triggers the workflow on push or pull request events but only for the master branch push: branches: [ master ] paths-ignore: - 'doc/**' - 'resources/**' - '**.md' pull_request: branches: [ master ] paths-ignore: - 'doc/**' - 'resources/**' - '**.md' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ ubuntu-latest, windows-latest ] java-version: [ 8, 11, 16, 17 ] java-distribution: [ adopt, adopt-openj9 ] # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout Codebase uses: actions/checkout@v2 - name: Setup Java Version uses: actions/setup-java@v3.14.1 with: java-version: ${{ matrix.java-version }} distribution: ${{ matrix.java-distribution }} architecture: x64 cache: 'maven' # Runs a single command using the runners shell - name: Build with Maven run: mvn clean package ================================================ FILE: .github/workflows/license-checker.yml ================================================ name: License checker on: push: branches: - maste paths-ignore: - 'doc/**' - 'resources/**' - '**.md' pull_request: branches: - master paths-ignore: - 'doc/**' - 'resources/**' - '**.md' jobs: check-license: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Check License Header uses: apache/skywalking-eyes@main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: log: info ================================================ FILE: .gitignore ================================================ .idea/ *.iml target/ dependency-reduced-pom.xml *.class *.swp **/*.versionsBackup .patch .classpath .factorypath .project .settings .DS_Store .vscode logs/ tmp/ ================================================ FILE: .licenserc.yaml ================================================ header: license: content: | Copyright (c) 2022, MegaEase All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. paths: - './' paths-ignore: - '.editorconfig' - 'install' - 'release' - '**/target/**' - '**/*.xml' - '**/*.yaml' - '**/*.properties' - '**/*.md' - '**/*.iml' - 'LICENSE' - '.github/' - '.git/' - 'doc/' - '**/resources/**' - 'CHANGELOG.md' - '.gitignore' - 'README.md' comment: on-failure dependency: files: - go.mod ================================================ FILE: AOSP-Checkstyles.xml ================================================ ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at service@megaease.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # EaseAgent A lightweight & opening Java Agent for Cloud-Native and APM system - [EaseAgent](#easeagent) - [Overview](#overview) - [Purpose](#purpose) - [Principles](#principles) - [Features](#features) - [Architecture Diagram](#architecture-diagram) - [Description](#description) - [QuickStart](#quickstart) - [Get And Set Environment Variable](#get-and-set-environment-variable) - [Setup Environment Variable](#setup-environment-variable) - [Download](#download) - [Build From the Source](#build-from-the-source) - [Get Configuration file](#get-configuration-file) - [Monitor Spring Petclinic](#monitor-spring-petclinic) - [Prerequisites](#prerequisites) - [Initialize and Start the project](#initialize-and-start-the-project) - [Metric](#metric) - [Tracing](#tracing) - [Build Spring Petclinic](#build-spring-petclinic) - [Add an Enhancement Plugin](#add-an-enhancement-plugin) - [User Manual](#user-manual) - [Enhancement Plugin Development Guide](#enhancement-plugin-development-guide) - [Report Plugin Development Guide](#report-plugin-development-guide) - [Community](#community) - [Licenses](#licenses) ## Overview - EaseAgent is the underlying component that provides non-intrusive extensions to applications of the Java ecosystem. - EaseAgent can collect distributed application tracing, metrics, and logs, which could be used in the APM system and improve the observability of a distributed system. for the tracing, EaseAgent follows the [Google Dapper](https://research.google/pubs/pub36356/) paper. - EaseAgent also can work with Cloud-Native architecture. For example, it can help Service Mesh (especially for [EaseMesh](https://github.com/megaease/easemesh/) ) to do some control panel work. - EaseAgent supports plugins mechanism development, which is easy to extend or add new functionality. ### Purpose - EaseAgent can be a Java agent for APM(Application Performance Management) system. - EaseAgent collects the basic metrics and the service tracing logs, which is very helpful for performance analysis and troubleshooting. - EaseAgent is compatible with mainstream monitoring ecosystems, such as Kafka, ElasticSearch, Prometheus, Zipkin, etc. - EaseAgent majorly focuses on the Spring Boot development environments, but users can support any Java ecosystem applications through plugins. - EaseAgent can support scenario-specific business requirements through the plugin mechanism, such as traffic redirection, traffic coloring, etc. ### Principles - Safe to Java application/service. - Instrumenting a Java application in a non-intrusive way. - Lightweight and very low CPU, memory, and I/O resource usage. - Highly extensible, users can easily do extensions through a simple and clear plugin interface. - Design for Micro-Service architecture, collecting the data from a service perspective. ## Features * Easy to use. It is right out of the box for Metrics, Tracing and Logs collecting. * Collecting Metric & Tracing Logs. * `JDBC 4.0` * `HTTP Servlet`、`HTTP Filter` * `Spring Boot 2.2.x`: `WebClient` 、 `RestTemplate`、`FeignClient` * `RabbitMQ Client 5.x`、 `Kafka Client 2.4.x` * `Jedis 3.5.x`、 `Lettuce 5.3.x (sync、async)` * `ElasticSearch Client >= 7.x (sync、async)` * `Mongodb Client >=4.0.x (sync、async)` * `Motan` * `Dubbo` * `SofaRpc >= 5.3.0` * Collecting Access Logs. * `HTTP Servlet`、`HTTP Filter` * `Spring Cloud Gateway` * Instrumenting the `traceId` and `spanId` into user application logging automatically * Supplying the `health check` endpoint * Supplying the `readiness check` endpoint for `SpringBoot2.2.x` * Supplying the `agent info` endpoint * Data Reports * Console Reporter. * Prometheus Exports. * Http Reporter. * Kafka Reporter. * Custom Reporter. * Easy to Extend * Simple and clear Plugin Interface, creating a plugin as few as three classes. * Extremely cleanly packaged `Tracing` and `Metric` API, with a small amount of code to achieve business support. * Standardization * The tracing data format is fully compatible with the Zipkin data format. * Metric data format fully supports integration with `Prometheus`. * The application log format is fully compatible with the `Opentelemetry` data format. ## Architecture Diagram ![image](./doc/images/EaseAgent-Architecture-Base-v2.0.png) #### Description **Plugin Framework** in `core` module is base on [Byte buddy](https://github.com/raphw/byte-buddy) technology. 1. Easeagent's plugin defines where (which classes and methods) to make enhancements by implementing the `Points` and what to do at the point of enhancement by implementing the `Interceptor`. 2. When the program invokes the enhanced method of class defined by Points, the `unique index`(uid) owned by the method will be used as a parameter to call the common interface of `Agent Common Method Advice`, which finds the `Agent Interceptor Chain` by the `Unique Index` and calls the `before` method of each Interceptor in the chain in order of priority. 3. Normally, both the `Metric Interceptor` and the `Tracing Interceptor` are in the agent interceptor chain and are called sequentially. 4. According to call the `Metric API` and `Tracing API` in interceptors, the `Metric` and `Tracing` information will be stored in `MetricRegistry` and `Tracing`. 5. The `Reporter` module will get information from `MetricRegistry` and `Tracing` and send it to `Kafka`. 6. The `after` method of each interceptor in the `Agent Interceptor Chain` will be invoked in the reverse order of the `before` invoked at last. 7. The `tracing` data can be sent to `kafka` server or `zipkin` server, the `metric` data can be sent to `kafka` server and pull by `Prometheus` server. ## QuickStart ### Get And Set Environment Variable Setup Environment Variable and then download the latest release of `easeagent.jar` or build it from the source. #### Setup Environment Variable ``` $ cd ~/easeagent #[Replace with agent path] $ export EASE_AGENT_PATH=`pwd` # export EASE_AGENT_PATH=[Replace with agent path] $ mkdir plugins ``` #### Download Download `easeagent.jar` from releases [releases](https://github.com/megaease/easeagent/releases). ``` $ curl -Lk https://github.com/megaease/easeagent/releases/latest/download/easeagent.jar -O ``` #### Build From the Source You need Java 1.8+ and git: Download EaseAgent with `git clone https://github.com/megaease/easeagent.git`. ``` $ cd easeagent $ mvn clean package -Dmaven.test.skip $ cp ./build/target/easeagent-dep.jar $EASE_AGENT_PATH/easeagent.jar ``` The `./build/target/easeagent-dep.jar` is the agent jar with all the dependencies. > For the Windows platform, please make sure git `core.autocrlf` is set to false before git clone. > You can use `git config --global core.autocrlf false` to modify `core.autocrlf`. [How to use easeagent.jar on host?](doc/how-to-use/use-on-host.md) [How to use easeagent.jar in docker?](doc/how-to-use/use-in-docker.md) #### Get Configuration file Extracting the default configuration file. ``` $ cd $EASE_AGENT_PATH $ jar xf easeagent.jar agent.properties easeagent-log4j2.xml ``` By default, there is an agent.properties configuration file, which is configured to print all output data to the console. ### Monitor Spring Petclinic #### Prerequisites - Make sure you have installed the docker, docker-compose in your environment. - Make sure your docker version is higher than v19.+. - Make sure your docker-compose version is higher than v2.+. [Project Details](https://github.com/megaease/easeagent-spring-petclinic) #### Initialize and Start the project ``` $ git clone https://github.com/megaease/easeagent-spring-petclinic.git $ cd easeagent-spring-petclinic $ git submodule update --init $ ./spring-petclinic.sh start ``` > The script will download the latest release of EaseAgent. > If you want to use your own built EaseAgent, copy it to the directory: `easeagent/downloaded` >> ```$ cp $EASE_AGENT_PATH/easeagent.jar easeagent/downloaded/easeagent-latest.jar``` It requires `Docker` to pull images from the docker hub, be patient. Open Browser to visit grafana UI: [http://localhost:3000](http://localhost:3000). #### Metric Click the `search dashboards`, the first icon in the left menu bar. Choose the `spring-petclinic-easeagent` to open the dashboard we prepare for you. Prometheus Metric Schedule: [Prometheus Metric](doc/prometheus-metric-schedule.md) ![metric](doc/images/grafana-metric.png) #### Tracing If you want to check the tracing-data, you could click the explore in the left menu bar. Click the Search - beta to switch search mode. Click search query button in the right up corner, there is a list containing many tracing. Choose one to click. ![tracing](doc/images/grafana-tracing.png) #### Build Spring Petclinic [Spring Petclinic Demo](doc/spring-petclinic-demo.md) ### Add an Enhancement Plugin [Add a Demo Plugin to EaseAgent](doc/add-plugin-demo.md) ## User Manual For more information, please refer to the [User Manual](./doc/user-manual.md). ## Enhancement Plugin Development Guide Refer to [Plugin Development Guide](./doc/development-guide.md). ## Report Plugin Development Guide Report plugin enables user report tracing/metric data to different kinds of the backend in a different format. Refer to [Report Plugin Development Guide](./doc/report-development-guide.md) ## Community * [Github Issues](https://github.com/megaease/easeagent/issues) * [Join Slack Workspace](https://join.slack.com/t/openmegaease/shared_invite/zt-upo7v306-lYPHvVwKnvwlqR0Zl2vveA) for requirement, issue and development. * [MegaEase on Twitter](https://twitter.com/megaease) If you have any questions, welcome to discuss them in our community. Welcome to join! ## Licenses EaseAgent is licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for the full license text. ================================================ FILE: build/pom.xml ================================================ easeagent com.megaease.easeagent 2.3.0 4.0.0 build org.apache.kafka kafka-clients provided javax.servlet javax.servlet-api provided com.megaease.easeagent metrics ${project.version} com.megaease.easeagent zipkin ${project.version} com.megaease.easeagent log4j2-api ${project.version} com.megaease.easeagent core ${project.version} com.megaease.easeagent loader ${project.version} easeagent org.apache.maven.plugins maven-compiler-plugin 3.6.1 ${version.java} ${version.java} ${encoding.file} -Xlint:unchecked maven-antrun-plugin 3.0.0 prepare-package run org.apache.maven.plugins maven-shade-plugin ${version.maven-shade-plugin} true org.apache.logging.log4j:* ** package shade com.github.edwgiz maven-shade-plugin.log4j2-cachefile-transformer 2.14.0 pl.project13.maven git-commit-id-plugin 4.9.10 revision true false true yyyy-MM-dd'T'HH:mm:ssZ true ${project.build.outputDirectory}/git.properties git.commit.* org.apache.maven.plugins maven-assembly-plugin 3.0.0 src/assembly/src.xml true true com.megaease.easeagent.Main com.megaease.easeagent.StartBootstrap log4j.configurationFile ${version} false package single ================================================ FILE: build/src/assembly/src.xml ================================================ dep / jar ${project.build.outputDirectory} git.properties agent.yaml agent.properties user-minimal-cfg.properties easeagent-log4j2.xml ${project.basedir}/target/inner-plugins/ plugins original-*.jar *.jar ${project.basedir}/target/log4j2/ log4j2 *.jar ${project.basedir}/target/boot/ boot *.jar boot com.megaease.easeagent:plugin-api false lib **/com/megaease/easeagent/plugin/* **/com/megaease/easeagent/boot/* **/com/megaease/easeagent/log4j2/* com.megaease.easeagent:build com.megaease.easeagent:loader true ================================================ FILE: build/src/main/java/com/megaease/easeagent/StartBootstrap.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent; import com.megaease.easeagent.core.Bootstrap; import java.lang.instrument.Instrumentation; public class StartBootstrap { private StartBootstrap() {} public static void premain(String args, Instrumentation inst, String javaAgentJarPath) { Bootstrap.start(args, inst, javaAgentJarPath); } } ================================================ FILE: build/src/main/resources/agent-to-cloud_1.0.properties ================================================ # for v2.0 agent send to megacloud-v1.0 kafka topic setting plugin.observability.global.metric.topic=application-meter plugin.observability.access.metric.topic=application-log plugin.observability.redis.metric.topic=platform-meter plugin.observability.rabbit.metric.topic=platform-meter plugin.observability.kafka.metric.topic=platform-meter plugin.observability.elasticsearch.metric.topic=platform-meter plugin.observability.jvmGc.metric.topic=platform-meter plugin.observability.jvmMemory.metric.topic=platform-meter ================================================ FILE: build/src/main/resources/agent.properties ================================================ name=demo-service system=demo-system ### http server # When the enabled value = false, agent will not start the http server # You can use -Deaseagent.server.enabled=[true | false] to override. easeagent.server.enabled=true # http server port. You can use -Deaseagent.server.port=[port] to override. easeagent.server.port=9900 # Enable health/readiness easeagent.health.readiness.enabled=true plugin.integrability.global.healthReady.enabled=true # forwarded headers page # Pass-through headers from the root process all the way to the end # format: easeagent.progress.forwarded.headers={headerName} #easeagent.progress.forwarded.headers=X-Forwarded-For #easeagent.progress.forwarded.headers=X-Location,X-Mesh-Service-Canary,X-Phone-Os ### ### default tracings configuration ### # sampledType: ## counting: percentage sampling, sampled limit 0.01 to 1, 1 is always sample, 0 is never sample, 0.1 is ten samples per hundred ## rate_limiting: traces per second, sampled >= 0, 0 is never sample, 10 is max 10 traces per second ## boundary: percentage sampling by traceId, sampled limit 0.0001 to 1, 1 is always sample, 0 is never sample ## if sampled=0.001, when (traceId^random)%10000<=(0.001*10000) sampled ## sampledType must be used with sampled, otherwise the default value is used Sampler.ALWAYS_SAMPLE observability.tracings.sampledType= observability.tracings.sampled=1 # get header from response headers then tag to tracing span # format: observability.tracings.tag.response.headers.{key}={value} # support ease mesh # X-EG-Circuit-Breaker # X-EG-Retryer # X-EG-Rate-Limiter # X-EG-Time-Limiter observability.tracings.tag.response.headers.eg.0=X-EG-Circuit-Breaker observability.tracings.tag.response.headers.eg.1=X-EG-Retryer observability.tracings.tag.response.headers.eg.2=X-EG-Rate-Limiter observability.tracings.tag.response.headers.eg.3=X-EG-Time-Limiter # -------------------- plugin global config --------------------- plugin.observability.global.init.enabled=true plugin.observability.global.tracing.enabled=true plugin.observability.global.metric.enabled=true plugin.observability.global.metric.interval=30 plugin.observability.global.metric.topic=application-metrics plugin.observability.global.metric.url=/application-metrics ## # if different with reporter.outputServer.appendType, # following options can be used in user config file to override # the default or global one # ## when it's scrape by prometheus, noop can be used # plugin.observability.global.metric.appendType=noop ## for debug, console can be used # plugin.observability.global.metric.appendType=console # plugin.observability.global.metric.appendType=http # # add service name to header enabled by name for easemesh plugin.integrability.global.addServiceNameHead.enabled=true # redirect the middleware address when env has address, see: com.megaease.easeagent.plugin.api.middleware.RedirectProcessor # about redirect: jdbc, kafka, rabbitmq, redis, plugin.integrability.global.redirect.enabled=true # forwarded headers enabled. # headers see config: easeagent.progress.forwarded.headers.???=??? plugin.integrability.global.forwarded.enabled=true plugin.hook.global.foundation.enabled=true plugin.observability.global.log.enabled=true plugin.observability.global.log.topic=application-log plugin.observability.global.log.url=/application-log #plugin.observability.global.log.appendType=console plugin.observability.global.log.level=INFO plugin.observability.global.log.encoder=LogDataJsonEncoder #plugin.observability.global.log.encoder.collectMdcKeys= # support pattern: # "logLevel": "%-5level", # "threadId": "%thread", # "location": "%logger{36}", # "message": "%msg%n", plugin.observability.global.log.encoder.timestamp=%d{UNIX_MILLIS} plugin.observability.global.log.encoder.logLevel=%-5level plugin.observability.global.log.encoder.threadId=%thread plugin.observability.global.log.encoder.location=%logger{36} plugin.observability.global.log.encoder.message=%msg%n%xEx{3} # # -------------------- access --------------------- ## access: servlet and spring gateway plugin.observability.access.log.encoder=AccessLogJsonEncoder # plugin.observability.access.metric.appendType=kafka #plugin.observability.logback.log.enabled=false #plugin.observability.log4j2.log.enabled=false # ---------------------------------------------- # if the plugin configuration is consistent with the global namespace, # do not add configuration items not commented out in this default configuration file. # otherwise, they can not be overridden by Global configuration in user's configuration file. # # -------------------- jvm --------------------- # plugin.observability.jvmGc.metric.enabled=true # plugin.observability.jvmGc.metric.interval=30 plugin.observability.jvmGc.metric.topic=platform-metrics plugin.observability.jvmGc.metric.url=/platform-metrics # plugin.observability.jvmGc.metric.appendType=kafka # plugin.observability.jvmMemory.metric.enabled=true # plugin.observability.jvmMemory.metric.interval=30 plugin.observability.jvmMemory.metric.topic=platform-metrics plugin.observability.jvmMemory.metric.url=/platform-metrics # plugin.observability.jvmMemory.metric.appendType=kafka # # -------------------- async --------------------- # plugin.observability.async.tracing.enabled=true # # -------------------- elasticsearch redirect --------------------- # plugin.integrability.elasticsearch.redirect.enabled=true # plugin.observability.elasticsearch.tracing.enabled=true # elasticsearch metric # plugin.observability.elasticsearch.metric.enabled=true # plugin.observability.elasticsearch.metric.interval=30 plugin.observability.elasticsearch.metric.topic=platform-metrics plugin.observability.elasticsearch.metric.url=/platform-metrics # plugin.observability.elasticsearch.metric.appendType=kafka # # -------------------- httpServlet --------------------- # plugin.observability.httpServlet.tracing.enabled=true # plugin.observability.httpServlet.metric.enabled=true # plugin.observability.httpServlet.metric.interval=30 # plugin.observability.httpServlet.metric.topic=application-metrics # plugin.observability.httpServlet.metric.url=/application-metrics # plugin.observability.httpServlet.metric.appendType=kafka # # # -------------------- tomcat --------------------- # plugin.observability.tomcat.tracing.enabled=true # plugin.observability.tomcat.metric.enabled=true # plugin.observability.tomcat.metric.interval=30 # plugin.observability.tomcat.metric.topic=application-metrics # plugin.observability.tomcat.metric.url=/application-metrics # plugin.observability.tomcat.metric.appendType=kafka # # -------------------- jdbc --------------------- ## jdbc tracing # plugin.observability.jdbc.tracing.enabled=true # jdbcStatement metric # plugin.observability.jdbcStatement.metric.enabled=true # plugin.observability.jdbcStatement.metric.interval=30 # plugin.observability.jdbcStatement.metric.topic=application-metrics # plugin.observability.jdbcStatement.metric.url=/application-metrics # plugin.observability.jdbcStatement.metric.appendType=kafka ## jdbcConnection metric # plugin.observability.jdbcConnection.metric.enabled=true # plugin.observability.jdbcConnection.metric.interval=30 # plugin.observability.jdbcConnection.metric.topic=application-metrics # plugin.observability.jdbcConnection.metric.url=/application-metrics # plugin.observability.jdbcConnection.metric.appendType=kafka ## sql compress ## compress.enabled=true, can use md5Dictionary to compress ## compress.enabled=false, use original sql plugin.observability.jdbc.sql.compress.enabled=true ## md5Dictionary metric # plugin.observability.md5Dictionary.metric.enabled=true # plugin.observability.md5Dictionary.metric.interval=30 # plugin.observability.md5Dictionary.metric.topic=application-metrics # plugin.observability.md5Dictionary.metric.url=/application-metrics # plugin.observability.md5Dictionary.metric.appendType=kafka ## jdbc redirect # plugin.integrability.jdbc.redirect.enabled=true # # -------------------- kafka --------------------- # kafka tracing # plugin.observability.kafka.tracing.enabled=true # kafka metric # plugin.observability.kafka.metric.enabled=true # plugin.observability.kafka.metric.interval=30 plugin.observability.kafka.metric.topic=platform-metrics plugin.observability.kafka.metric.url=/platform-metrics # plugin.observability.kafka.metric.appendType=kafka # kafka redirect # plugin.integrability.kafka.redirect.enabled=true # # -------------------- rabbitmq --------------------- # rabbitmq tracing # plugin.observability.rabbitmq.tracing.enabled=true # rabbitmq metric # plugin.observability.rabbitmq.metric.enabled=true # plugin.observability.rabbitmq.metric.interval=30 plugin.observability.rabbitmq.metric.topic=platform-metrics plugin.observability.rabbitmq.metric.url=/platform-metrics # plugin.observability.rabbitmq.metric.appendType=kafka # rabbitmq redirect # plugin.integrability.rabbitmq.redirect.enabled=true # # -------------------- redis --------------------- # redis tracing # plugin.observability.redis.tracing.enabled=true # redis metric # plugin.observability.redis.metric.enabled=true # plugin.observability.redis.metric.interval=30 plugin.observability.redis.metric.topic=platform-metrics plugin.observability.redis.metric.url=/platform-metrics # plugin.observability.redis.metric.appendType=kafka # redis redirect # plugin.integrability.redis.redirect.enabled=true # # -------------------- springGateway --------------------- # springGateway tracing # plugin.observability.springGateway.tracing.enabled=true # springGateway metric # plugin.observability.springGateway.metric.enabled=true # plugin.observability.springGateway.metric.interval=30 # plugin.observability.springGateway.metric.topic=application-metrics # plugin.observability.springGateway.metric.url=/application-metrics # plugin.observability.springGateway.metric.appendType=kafka # # -------------------- request --------------------- ## httpclient tracing\uFF1Ahttpclient and httpclient5 # plugin.observability.httpclient.tracing.enabled=true ## okHttp tracing # plugin.observability.okHttp.tracing.enabled=true ## webclient tracing # plugin.observability.webclient.tracing.enabled=true ## feignClient tracing # plugin.observability.feignClient.tracing.enabled=true ## restTemplate tracing # plugin.observability.restTemplate.tracing.enabled=true ## httpURLConnection tracing # plugin.observability.httpURLConnection.tracing.enabled=true # -------------------- service name --------------------- ## add service name to header by name for easemesh. default name: X-Mesh-RPC-Service # plugin.integrability.serviceName.addServiceNameHead.propagate.head=X-Mesh-RPC-Service # # -------------------- mongodb --------------------- ## mongodb tracing # plugin.observability.mongodb.tracing.enabled=true ## mongodb metric # plugin.observability.mongodb.metric.enabled=true # plugin.observability.mongodb.metric.interval=30 plugin.observability.mongodb.metric.topic=platform-metrics plugin.observability.mongodb.metric.url=/platform-metrics # plugin.observability.mongodb.metric.appendType=kafka ## mongodb redirect # plugin.integrability.mongodb.redirect.enabled=true ## mongodb foundation # plugin.hook.mongodb.foundation.enabled=true # # -------------------- motan --------------------- # motan tracing # plugin.observability.motan.tracing.enabled=true ## motan args collect switch # plugin.observability.motan.tracing.args.collect.enabled=false ## motan result collect switch # plugin.observability.motan.tracing.result.collect.enabled=false # motan metric # plugin.observability.motan.metric.enabled=true # plugin.observability.motan.metric.interval=30 plugin.observability.motan.metric.topic=platform-metrics plugin.observability.motan.metric.url=/platform-metrics # plugin.observability.motan.metric.appendType=kafka # # -------------------- dubbo --------------------- ## dubbo tracing #plugin.observability.dubbo.tracing.enabled=true ## dubbo arguments collect switch #plugin.observability.dubbo.tracing.args.collect.enabled=false ## dubbo return result collect switch #plugin.observability.dubbo.tracing.result.collect.enabled=false ## dubbo metric #plugin.observability.dubbo.metric.enabled=true #plugin.observability.dubbo.metric.interval=30 #plugin.observability.dubbo.metric.topic=platform-metrics #plugin.observability.dubbo.metric.url=/platform-metrics # plugin.observability.dubbo.metric.appendType=kafka # -------------------- sofarpc --------------------- # sofarpc tracing # plugin.observability.sofarpc.tracing.enabled=true ## sofarpc args collect switch # plugin.observability.sofarpc.tracing.args.collect.enabled=false ## sofarpc result collect switch # plugin.observability.sofarpc.tracing.result.collect.enabled=false # sofarpc metric # plugin.observability.sofarpc.metric.enabled=true # plugin.observability.sofarpc.metric.interval=30 plugin.observability.sofarpc.metric.topic=platform-metrics plugin.observability.sofarpc.metric.url=/platform-metrics # plugin.observability.sofarpc.metric.appendType=kafka # -------------- output ------------------ ## http/kafka/zipkin server host and port for tracing and metric ###### example ###### ## http: [http|https]://127.0.0.1:8080/report ## kafka: 192.168.1.2:9092, 192.168.1.3:9092, 192.168.1.3:9092 ## zipkin: [http|https]://127.0.0.1:8080/zipkin reporter.outputServer.bootstrapServer=127.0.0.1:9092 reporter.outputServer.appendType=console reporter.outputServer.timeout=1000 ## enabled=false: disable output tracing and metric ## enabled=true: output tracing and metric reporter.outputServer.enabled=true ## username and password for http basic auth reporter.outputServer.username= reporter.outputServer.password= ## enable=false: disable mtls ## enable=true: enable tls ## key, cert, ca_cert is enabled when tls.enable=true reporter.outputServer.tls.enable=false reporter.outputServer.tls.key= reporter.outputServer.tls.cert= reporter.outputServer.tls.ca_cert= # --- redefine to output properties reporter.log.output.messageMaxBytes=999900 reporter.log.output.reportThread=1 reporter.log.output.queuedMaxSpans=1000 reporter.log.output.queuedMaxSize=1000000 reporter.log.output.messageTimeout=1000 ## sender.appendType config ## [http] send to http server ## [kafka] send to kafka ## [console] send to console ## reporter.log.sender.appendType=console ## enabled=true: # reporter.log.sender.enabled=true # reporter.log.sender.url=/application-log ## sender.appendType config ## [http] send to http server ## [kafka] send to kafka ## [console] send to console # reporter.tracing.sender.appendType=http # reporter.tracing.sender.appendType=console ## enabled=true: reporter.tracing.sender.enabled=true ## url is only used in http ## append to outputServer.bootstrapServer ###### example ###### ## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report ## reporter.tracing.sender.url=/tracing ## final output url: http://127.0.0.1:8080/report/tracing ## if url is start with [http|https], url override reporter.outputServer.bootstrapServer ###### example ###### ## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report ## reporter.tracing.sender.url=http://127.0.0.10:9090/tracing ## final output url: http://127.0.0.10:9090/tracing reporter.tracing.sender.url=/application-tracing-log ## topic for kafka use reporter.tracing.sender.topic=application-tracing-log reporter.tracing.encoder=SpanJsonEncoder # --- redefine to output properties reporter.tracing.output.messageMaxBytes=999900 reporter.tracing.output.reportThread=1 reporter.tracing.output.queuedMaxSpans=1000 reporter.tracing.output.queuedMaxSize=1000000 reporter.tracing.output.messageTimeout=1000 ## sender.appendType config ## [http] send to http server ## [metricKafka] send to kafka ## [console] send to console #reporter.metric.sender.appendType=http #reporter.metric.sender.appendType=console ## url is only used in http ## append to outputServer.bootstrapServer ###### example ###### ## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report ## reporter.metric.sender.url=/metric ## final output url: http://127.0.0.1:8080/report/metric ## if url is start with [http|https], url override reporter.outputServer.bootstrapServer ###### example ###### ## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report ## reporter.metric.sender.url=http://127.0.0.10:9090/metric ## final output url: http://127.0.0.10:9090/metric #reporter.metric.sender.url=/metrics ## support spring boot 3.5.3: jdk17+3.5.3 #runtime.code.version.points.jdk=jdk17 #runtime.code.version.points.spring-boot=3.x.x ================================================ FILE: build/src/main/resources/easeagent-log4j2.xml ================================================ %d [%10.10t] %5p %10.10c{1} - %msg%n ================================================ FILE: build/src/main/resources/user-minimal-cfg.properties ================================================ ## This a minimal configuration required to start the EaseAgent. ## In most cases, this is all the configuration items that the normal user needs to be concerned about. ## ## When the user specifies a user configration file as this one, the items in user config file will override the default ## configuration in agent.properties packaged in easeagent.jar ## ## -Deaseagent.config.path=/path/to/user-cfg-file name=demo-springweb system=demo-system ### ### report configuration ### reporter.outputServer.bootstrapServer=http://127.0.0.1:9411 reporter.outputServer.appendType=console ## ## Global metric configuration ## the appendType is same as outputServer, so comment out # plugin.observability.global.metric.appendType=console ## ## tracing sender ## [http] send to http server ## [kafka] send to kafka ## [console] send to console # reporter.tracing.sender.appendType= # reporter.tracing.sender.url=http://tempo:9411/api/v2/spans reporter.tracing.sender.url=http://localhost:9411/api/v2/spans ## access log sender ## [http] send to http server ## [kafka] send to kafka ## [console] send to console ## the appendType is same as outputServer, so comment out ## reporter.log.sender.appendType=console ================================================ FILE: config/pom.xml ================================================ easeagent com.megaease.easeagent 2.3.0 4.0.0 config com.megaease.easeagent plugin-api com.fasterxml.jackson.core jackson-databind com.megaease.easeagent log4j2-api org.slf4j slf4j-api com.megaease.easeagent log4j2-mock ${project.version} test org.yaml snakeyaml com.google.guava guava com.github.stefanbirkner system-rules 1.19.0 test ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/AutoRefreshConfigItem.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.api.config.Config; import java.util.function.BiFunction; public class AutoRefreshConfigItem { private volatile T value; public AutoRefreshConfigItem(Config config, String name, BiFunction func) { ConfigUtils.bindProp(name, config, func, v -> this.value = v); } public T getValue() { return value; } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/CompatibilityConversion.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.api.ProgressFields; import com.megaease.easeagent.plugin.api.config.ConfigConst; import javax.annotation.Nonnull; import java.util.*; import java.util.function.BiFunction; public class CompatibilityConversion { private static final Logger LOGGER = LoggerFactory.getLogger(CompatibilityConversion.class); protected static final String[] REQUEST_NAMESPACE = new String[]{ ConfigConst.Namespace.HTTPCLIENT, ConfigConst.Namespace.OK_HTTP, ConfigConst.Namespace.WEB_CLIENT, ConfigConst.Namespace.FEIGN_CLIENT, ConfigConst.Namespace.REST_TEMPLATE, }; private static final Map>> KEY_TO_NAMESPACE; private static final Set METRIC_SKIP; private static final Set TRACING_SKIP; static { Map>> map = new HashMap<>(); map.put(ConfigConst.Observability.KEY_METRICS_ACCESS, SingleBuilder.observability(ConfigConst.Namespace.ACCESS)); map.put(ConfigConst.Observability.KEY_METRICS_REQUEST, MultipleBuilder.observability(Arrays.asList(REQUEST_NAMESPACE))); map.put(ConfigConst.Observability.KEY_METRICS_JDBC_STATEMENT, SingleBuilder.observability(ConfigConst.Namespace.JDBC_STATEMENT)); map.put(ConfigConst.Observability.KEY_METRICS_JDBC_CONNECTION, SingleBuilder.observability(ConfigConst.Namespace.JDBC_CONNECTION)); map.put(ConfigConst.Observability.KEY_METRICS_MD5_DICTIONARY, SingleBuilder.observability(ConfigConst.Namespace.MD5_DICTIONARY)); map.put(ConfigConst.Observability.KEY_METRICS_RABBIT, SingleBuilder.observability(ConfigConst.Namespace.RABBITMQ)); map.put(ConfigConst.Observability.KEY_METRICS_KAFKA, SingleBuilder.observability(ConfigConst.Namespace.KAFKA)); map.put(ConfigConst.Observability.KEY_METRICS_CACHE, SingleBuilder.observability(ConfigConst.Namespace.REDIS)); map.put(ConfigConst.Observability.KEY_METRICS_JVM_GC, null); map.put(ConfigConst.Observability.KEY_METRICS_JVM_MEMORY, null); map.put(ConfigConst.Observability.KEY_TRACE_REQUEST, MultipleBuilder.observability(Arrays.asList(REQUEST_NAMESPACE))); map.put(ConfigConst.Observability.KEY_TRACE_REMOTE_INVOKE, SingleBuilder.observability(ConfigConst.Namespace.WEB_CLIENT)); map.put(ConfigConst.Observability.KEY_TRACE_KAFKA, SingleBuilder.observability(ConfigConst.Namespace.KAFKA)); map.put(ConfigConst.Observability.KEY_TRACE_JDBC, SingleBuilder.observability(ConfigConst.Namespace.JDBC)); map.put(ConfigConst.Observability.KEY_TRACE_CACHE, SingleBuilder.observability(ConfigConst.Namespace.REDIS)); map.put(ConfigConst.Observability.KEY_TRACE_RABBIT, SingleBuilder.observability(ConfigConst.Namespace.RABBITMQ)); KEY_TO_NAMESPACE = map; TRACING_SKIP = new HashSet<>(); TRACING_SKIP.add(ConfigConst.Observability.KEY_COMM_ENABLED); TRACING_SKIP.add(ConfigConst.Observability.KEY_COMM_SAMPLED_TYPE); TRACING_SKIP.add(ConfigConst.Observability.KEY_COMM_SAMPLED); TRACING_SKIP.add(ConfigConst.Observability.KEY_COMM_OUTPUT); TRACING_SKIP.add(ConfigConst.Observability.KEY_COMM_TAG); METRIC_SKIP = new HashSet<>(); METRIC_SKIP.add(ConfigConst.Observability.KEY_METRICS_JVM_GC); METRIC_SKIP.add(ConfigConst.Observability.KEY_METRICS_JVM_MEMORY); } public static Map transform(Map oldConfigs) { Map changedKeys = new HashMap<>(); Map newConfigs = new HashMap<>(); for (Map.Entry entry : oldConfigs.entrySet()) { Conversion conversion = transformConversion(entry.getKey()); Object changed = conversion.transform(newConfigs, entry.getValue()); if (conversion.isChange()) { changedKeys.put(entry.getKey(), changed); } } if (changedKeys.isEmpty()) { return oldConfigs; } if (LOGGER.isInfoEnabled()) { LOGGER.info("config key has transform: "); for (Map.Entry entry : changedKeys.entrySet()) { LOGGER.info("{} to {}", entry.getKey(), entry.getValue()); } } return newConfigs; } private static Conversion transformConversion(String key) { if (key.startsWith("observability.metrics.")) { return metricConversion(key); } else if (key.startsWith("observability.tracings.")) { return tracingConversion(key); } else if (key.startsWith(ConfigConst.GlobalCanaryLabels.SERVICE_HEADERS + ".")) { // return forwardedHeadersConversion(key); } return new FinalConversion(key, false); } private static Conversion metricConversion(String key) { if (key.equals(ConfigConst.Observability.METRICS_ENABLED)) { return new MultipleFinalConversion(Arrays.asList(new FinalConversion(ConfigConst.Observability.METRICS_ENABLED, true), new FinalConversion(ConfigConst.Plugin.OBSERVABILITY_GLOBAL_METRIC_ENABLED, true)), true); } return conversion(key, METRIC_SKIP, ConfigConst.PluginID.METRIC); } private static Conversion tracingConversion(String key) { if (key.equals(ConfigConst.Observability.TRACE_ENABLED)) { return new FinalConversion(ConfigConst.Plugin.OBSERVABILITY_GLOBAL_TRACING_ENABLED, true); } return conversion(key, TRACING_SKIP, ConfigConst.PluginID.TRACING); } // private static Conversion forwardedHeadersConversion(String key) { // return new FinalConversion(key.replace(ConfigConst.GlobalCanaryLabels.SERVICE_HEADERS + ".", ProgressFields.EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG + "."), true); // } private static Conversion conversion(String key, Set skipSet, String pluginId) { String[] keys = ConfigConst.split(key); if (keys.length < 4) { return new FinalConversion(key, false); } String key2 = keys[2]; if (skipSet.contains(key2)) { return new FinalConversion(key, false); } BiFunction> builder = KEY_TO_NAMESPACE.get(key2); if (builder == null) { builder = SingleBuilder.observability(key2); } String[] properties = new String[keys.length - 3]; int index = 0; for (int i = 3; i < keys.length; i++) { properties[index++] = keys[i]; } return builder.apply(pluginId, ConfigConst.join(properties)); } interface Conversion { K transform(Map configs, String value); boolean isChange(); } static class FinalConversion implements Conversion { private final String key; private final boolean change; public FinalConversion(String key, boolean change) { this.key = key; this.change = change; } @Override public String transform(Map configs, String value) { configs.put(key, value); return key; } public boolean isChange() { return change; } } static class MultipleFinalConversion implements Conversion> { private final List conversions; private final boolean change; MultipleFinalConversion(@Nonnull List conversions, boolean change) { this.conversions = conversions; this.change = change; } @Override public List transform(Map configs, String value) { List result = new ArrayList<>(); for (FinalConversion conversion : conversions) { result.add(conversion.transform(configs, value)); } return result; } @Override public boolean isChange() { return change; } } static class SingleConversion implements Conversion { private final String domain; private final String namespace; private final String id; private final String properties; public SingleConversion(String domain, String namespace, String id, String properties) { this.domain = domain; this.namespace = namespace; this.id = id; this.properties = properties; } @Override public String transform(Map configs, String value) { String key = ConfigUtils.buildPluginProperty(domain, namespace, id, properties); configs.put(key, value); return key; } @Override public boolean isChange() { return true; } } static class MultipleConversion implements Conversion> { private final String domain; private final List namespaces; private final String id; private final String properties; public MultipleConversion(String domain, List namespaces, String id, String properties) { this.domain = domain; this.namespaces = namespaces; this.id = id; this.properties = properties; } @Override public List transform(Map configs, String value) { List keys = new ArrayList<>(); for (String namespace : namespaces) { String key = ConfigUtils.buildPluginProperty(domain, namespace, id, properties); keys.add(key); configs.put(key, value); } return keys; } @Override public boolean isChange() { return true; } } static class SingleBuilder implements BiFunction> { private final String domain; private final String namespace; public SingleBuilder(String domain, String namespace) { this.domain = domain; this.namespace = namespace; } @Override public Conversion apply(String id, String properties) { return new SingleConversion(domain, namespace, id, properties); } static SingleBuilder observability(String namespace) { return new SingleBuilder(ConfigConst.OBSERVABILITY, namespace); } } static class MultipleBuilder implements BiFunction> { private final String domain; private final List namespaces; public MultipleBuilder(String domain, List namespaces) { this.domain = domain; this.namespaces = namespaces; } @Override public Conversion apply(String id, String properties) { return new MultipleConversion(domain, namespaces, id, properties); } static MultipleBuilder observability(List namespaces) { return new MultipleBuilder(ConfigConst.OBSERVABILITY, namespaces); } } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/ConfigAware.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.api.config.Config; public interface ConfigAware { void setConfig(Config config); } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/ConfigFactory.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.utils.ImmutableMap; import com.megaease.easeagent.plugin.utils.SystemEnv; import com.megaease.easeagent.plugin.utils.common.JsonUtil; import com.megaease.easeagent.plugin.utils.common.StringUtils; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; public class ConfigFactory { private static final Logger LOGGER = LoggerFactory.getLogger(ConfigFactory.class); private static final String CONFIG_PROP_FILE = "agent.properties"; private static final String CONFIG_YAML_FILE = "agent.yaml"; public static final String AGENT_CONFIG_PATH_PROP_KEY = "easeagent.config.path"; public static final String AGENT_SERVICE = "name"; public static final String AGENT_SYSTEM = "system"; public static final String AGENT_SERVER_PORT = "easeagent.server.port"; public static final String AGENT_SERVER_ENABLED = "easeagent.server.enabled"; public static final String EASEAGENT_ENV_CONFIG = "EASEAGENT_ENV_CONFIG"; private static final Map AGENT_CONFIG_KEYS_TO_PROPS = ImmutableMap.builder() .put("easeagent.name", AGENT_SERVICE) .put("easeagent.system", AGENT_SYSTEM) .put("easeagent.server.port", AGENT_SERVER_PORT) .put("easeagent.server.enabled", AGENT_SERVER_ENABLED) .build(); // OTEL_SERVICE_NAME=xxx private static final Map AGENT_ENV_KEY_TO_PROPS = new HashMap<>(); static { for (Map.Entry entry : AGENT_CONFIG_KEYS_TO_PROPS.entrySet()) { // dot.case -> UPPER_UNDERSCORE AGENT_ENV_KEY_TO_PROPS.put( ConfigPropertiesUtils.toEnvVarName(entry.getKey()), entry.getValue() ); } } /** * update config value from environment variables and java properties *

* java properties > environment variables > env:EASEAGENT_ENV_CONFIG={} > default */ static Map updateEnvCfg() { Map envCfg = new TreeMap<>(); String configEnv = SystemEnv.get(EASEAGENT_ENV_CONFIG); if (StringUtils.isNotEmpty(configEnv)) { Map map = JsonUtil.toMap(configEnv); Map strMap = new HashMap<>(); if (!map.isEmpty()) { for (Map.Entry entry : map.entrySet()) { strMap.put(entry.getKey(), entry.getValue().toString()); } } envCfg.putAll(strMap); } // override by environment variables, eg: export EASEAGENT_NAME=xxx for (Map.Entry entry : AGENT_ENV_KEY_TO_PROPS.entrySet()) { String value = SystemEnv.get(entry.getKey()); if (!StringUtils.isEmpty(value)) { envCfg.put(entry.getValue(), value); } } // override by java properties; eg: java -Deaseagent.name=xxx for (Map.Entry entry : AGENT_CONFIG_KEYS_TO_PROPS.entrySet()) { String value = System.getProperty(entry.getKey()); if (!StringUtils.isEmpty(value)) { envCfg.put(entry.getValue(), value); } } return envCfg; } private ConfigFactory() { } /** * Get config file path from system properties or environment variables */ public static String getConfigPath() { // get config path from -Deaseagent.config.path=/easeagent/agent.properties || export EASEAGENT_CONFIG_PATH=/easeagent/agent.properties String path = ConfigPropertiesUtils.getString(AGENT_CONFIG_PATH_PROP_KEY); if (StringUtils.isEmpty(path)) { // eg: -Dotel.javaagent.configuration-file=/easeagent/agent.properties || export OTEL_JAVAAGENT_CONFIGURATION_FILE=/easeagent/agent.properties path = OtelSdkConfigs.getConfigPath(); } return path; } public static GlobalConfigs loadConfigs(String pathname, ClassLoader loader) { // load property configuration file if exist GlobalConfigs configs = loadDefaultConfigs(loader, CONFIG_PROP_FILE); // load yaml configuration file if exist GlobalConfigs yConfigs = loadDefaultConfigs(loader, CONFIG_YAML_FILE); configs.mergeConfigs(yConfigs); // override by user special config file if (StringUtils.isNotEmpty(pathname)) { GlobalConfigs configsFromOuterFile = ConfigLoader.loadFromFile(new File(pathname)); LOGGER.info("Loaded user special config file: {}", pathname); configs.mergeConfigs(configsFromOuterFile); } // override by opentelemetry sdk env config configs.updateConfigsNotNotify(OtelSdkConfigs.updateEnvCfg()); // check environment cfg override configs.updateConfigsNotNotify(updateEnvCfg()); if (LOGGER.isDebugEnabled()) { final String display = configs.toPrettyDisplay(); LOGGER.debug("Loaded conf:\n{}", display); } return configs; } private static GlobalConfigs loadDefaultConfigs(ClassLoader loader, String file) { GlobalConfigs globalConfigs = JarFileConfigLoader.load(file); if (globalConfigs != null) { return globalConfigs; } return ConfigLoader.loadFromClasspath(loader, file); } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/ConfigLoader.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.config.yaml.YamlReader; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import org.yaml.snakeyaml.parser.ParserException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; public class ConfigLoader { private static final Logger LOGGER = LoggerFactory.getLogger(ConfigLoader.class); private static boolean checkYaml(String filename) { return filename.endsWith(".yaml") || filename.endsWith(".yml"); } static GlobalConfigs loadFromFile(File file) { try (FileInputStream in = new FileInputStream(file)) { return ConfigLoader.loadFromStream(in, file.getAbsolutePath()); } catch (IOException e) { LOGGER.warn("Load config file failure: {}", file.getAbsolutePath()); } return new GlobalConfigs(Collections.emptyMap()); } static GlobalConfigs loadFromStream(InputStream in, String filename) throws IOException { if (in != null) { Map map; if (checkYaml(filename)) { try { map = new YamlReader().load(in).compress(); } catch (ParserException e) { LOGGER.warn("Wrong Yaml format, load config file failure: {}", filename); map = Collections.emptyMap(); } } else { map = extractPropsMap(in); } return new GlobalConfigs(map); } else { return new GlobalConfigs(Collections.emptyMap()); } } private static HashMap extractPropsMap(InputStream in) throws IOException { Properties properties = new Properties(); properties.load(in); HashMap map = new HashMap<>(); for (String one : properties.stringPropertyNames()) { map.put(one, properties.getProperty(one)); } return map; } static GlobalConfigs loadFromClasspath(ClassLoader classLoader, String file) { try (InputStream in = classLoader.getResourceAsStream(file)) { return ConfigLoader.loadFromStream(in, file); } catch (IOException e) { LOGGER.warn("Load config file:{} by classloader:{} failure: {}", file, classLoader.toString(), e); } return new GlobalConfigs(Collections.emptyMap()); } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/ConfigManagerMXBean.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import java.io.IOException; import java.util.List; import java.util.Map; public interface ConfigManagerMXBean { void updateConfigs(Map configs); void updateService(String json, String version) throws IOException; void updateCanary(String json, String version) throws IOException; void updateService2(Map configs, String version); void updateCanary2(Map configs, String version); Map getConfigs(); List availableConfigNames(); default void healthz() { } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/ConfigNotifier.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.api.config.ChangeItem; import com.megaease.easeagent.plugin.api.config.ConfigChangeListener; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; public class ConfigNotifier { private static final Logger LOGGER = LoggerFactory.getLogger(ConfigNotifier.class); private final CopyOnWriteArrayList listeners = new CopyOnWriteArrayList<>(); private final String prefix; public ConfigNotifier(String prefix) { this.prefix = prefix; } public Runnable addChangeListener(ConfigChangeListener listener) { final boolean add = listeners.add(listener); return () -> { if (add) { listeners.remove(listener); } }; } public void handleChanges(List list) { final List changes = this.prefix.isEmpty() ? list : filterChanges(list); if (changes.isEmpty()) { return; } listeners.forEach(one -> { try { one.onChange(changes); } catch (Exception e) { LOGGER.warn("Notify config changes to listener failure: {}", e); } }); } private List filterChanges(List list) { return list.stream().filter(one -> one.getFullName().startsWith(prefix)) .map(e -> new ChangeItem(e.getFullName().substring(prefix.length()), e.getFullName(), e.getOldValue(), e.getNewValue())) .collect(Collectors.toList()); } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/ConfigPropertiesUtils.java ================================================ /* * Copyright (c) 2022, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.utils.SystemEnv; import javax.annotation.Nullable; import java.util.Locale; /** * Get config from system properties or environment variables. */ final class ConfigPropertiesUtils { public static boolean getBoolean(String propertyName, boolean defaultValue) { String strValue = getString(propertyName); return strValue == null ? defaultValue : Boolean.parseBoolean(strValue); } public static int getInt(String propertyName, int defaultValue) { String strValue = getString(propertyName); if (strValue == null) { return defaultValue; } try { return Integer.parseInt(strValue); } catch (NumberFormatException ignored) { return defaultValue; } } @Nullable public static String getString(String propertyName) { String value = System.getProperty(propertyName); if (value != null) { return value; } return SystemEnv.get(toEnvVarName(propertyName)); } /** * dot.case -> UPPER_UNDERSCORE */ public static String toEnvVarName(String propertyName) { return propertyName.toUpperCase(Locale.ROOT).replace('-', '_').replace('.', '_'); } private ConfigPropertiesUtils() { } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/ConfigUtils.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.megaease.easeagent.plugin.api.config.ChangeItem; import com.megaease.easeagent.plugin.api.config.Config; import com.megaease.easeagent.plugin.api.config.ConfigConst; import java.io.IOException; import java.util.*; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.stream.Collectors; import static com.megaease.easeagent.plugin.api.config.ConfigConst.*; public class ConfigUtils { private ConfigUtils() { } public static void bindProp(String name, Config configs, BiFunction func, Consumer consumer, R def) { Runnable process = () -> { R result = func.apply(configs, name); result = firstNotNull(result, def); if (result != null) { consumer.accept(result); } }; process.run(); configs.addChangeListener(list -> { boolean hasChange = list.stream().map(ChangeItem::getFullName).anyMatch(fn -> fn.equals(name)); if (hasChange) { process.run(); } }); } @SafeVarargs private static R firstNotNull(R... ars) { for (R one : ars) { if (one != null) { return one; } } return null; } public static void bindProp(String name, Config configs, BiFunction func, Consumer consumer) { bindProp(name, configs, func, consumer, null); } public static Map json2KVMap(String json) throws IOException { ObjectMapper mapper = new ObjectMapper(); JsonNode node = mapper.readTree(json); List> list = extractKVs(null, node); return list.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } public static List> extractKVs(String prefix, JsonNode node) { List> rst = new LinkedList<>(); if (node.isObject()) { Iterator names = node.fieldNames(); while (names.hasNext()) { String current = names.next(); rst.addAll(extractKVs(join(prefix, current), node.path(current))); } } else if (node.isArray()) { int len = node.size(); for (int i = 0; i < len; i++) { rst.addAll(extractKVs(join(prefix, i + ""), node.path(i))); } } else { rst.add(new AbstractMap.SimpleEntry<>(prefix, node.asText(""))); } return rst; } private static String join(String prefix, String current) { return prefix == null ? current : ConfigConst.join(prefix, current); } public static boolean isGlobal(String namespace) { return PLUGIN_GLOBAL.equals(namespace); } public static boolean isPluginConfig(String key) { return key != null && key.startsWith(PLUGIN_PREFIX); } public static boolean isPluginConfig(String key, String domain, String namespace, String id) { return key != null && key.startsWith(ConfigConst.join(PLUGIN, domain, namespace, id)); } public static PluginProperty pluginProperty(String path) { String[] configs = path.split("\\" + DELIMITER); if (configs.length < 5) { throw new ValidateUtils.ValidException(String.format("Property[%s] must be format: %s", path, ConfigConst.join(PLUGIN, "", "", "", ""))); } for (int idOffsetEnd = 3; idOffsetEnd < configs.length - 1; idOffsetEnd++) { new PluginProperty(configs[1], configs[2], ConfigConst.join(Arrays.copyOfRange(configs, 3, idOffsetEnd)), ConfigConst.join(Arrays.copyOfRange(configs, idOffsetEnd + 1, configs.length))); } return new PluginProperty(configs[1], configs[2], configs[3], ConfigConst.join(Arrays.copyOfRange(configs, 4, configs.length))); } public static String requireNonEmpty(String obj, String message) { if (obj == null || obj.trim().isEmpty()) { throw new ValidateUtils.ValidException(message); } return obj.trim(); } public static String buildPluginProperty(String domain, String namespace, String id, String property) { return String.format(PLUGIN_FORMAT, domain, namespace, id, property); } public static String buildCodeVersionKey(String key) { return RUNTIME_CODE_VERSION_POINTS_PREFIX + key; } /** * extract config item with a fromPrefix to and convert the prefix to 'toPrefix' for configuration Compatibility * * @param cfg config source map * @param fromPrefix from * @param toPrefix to * @return Extracted and converted KV map */ public static Map extractAndConvertPrefix(Map cfg, String fromPrefix, String toPrefix) { Map convert = new HashMap<>(); Set keys = new HashSet<>(); cfg.forEach((key, value) -> { if (key.startsWith(fromPrefix)) { keys.add(key); key = toPrefix + key.substring(fromPrefix.length()); convert.put(key, value); } }); // override, new configuration KV override previous KV convert.putAll(extractByPrefix(cfg, toPrefix)); return convert; } /** * Extract config items from config by prefix * * @param config config * @param prefix prefix * @return Extracted KV */ public static Map extractByPrefix(Config config, String prefix) { return extractByPrefix(config.getConfigs(), prefix); } public static Map extractByPrefix(Map cfg, String prefix) { Map extract = new TreeMap<>(); // override, new configuration KV override previous KV cfg.forEach((key, value) -> { if (key.startsWith(prefix)) { extract.put(key, value); } }); return extract; } public static int isChanged(String name, Map map, String check) { if (map.get(name) == null || map.get(name).equals(check)) { return 0; } return 1; } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/Configs.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.api.config.ChangeItem; import com.megaease.easeagent.plugin.api.config.Config; import com.megaease.easeagent.plugin.api.config.ConfigChangeListener; import java.util.*; import java.util.stream.Collectors; public class Configs implements Config { private static final Logger LOGGER = LoggerFactory.getLogger(Configs.class); protected Map source; protected ConfigNotifier notifier; protected Configs() { } public Configs(Map source) { this.source = new TreeMap<>(source); notifier = new ConfigNotifier(""); } public void updateConfigsNotNotify(Map changes) { this.source.putAll(changes); } public void updateConfigs(Map changes) { Map dump = new TreeMap<>(this.source); List items = new LinkedList<>(); changes.forEach((name, value) -> { String old = dump.get(name); if (!Objects.equals(old, value)) { dump.put(name, value); items.add(new ChangeItem(name, name, old, value)); } }); if (!items.isEmpty()) { LOGGER.info("change items: {}", items); this.source = dump; this.notifier.handleChanges(items); } } protected boolean hasText(String text) { return text != null && text.trim().length() > 0; } @Override public Map getConfigs() { return new TreeMap<>(this.source); } public String toPrettyDisplay() { return this.source.toString(); } public boolean hasPath(String path) { return this.source.containsKey(path); } public String getString(String name) { return this.source.get(name); } public String getString(String name, String defVal) { String val = this.source.get(name); return val == null ? defVal : val; } public Integer getInt(String name) { String value = this.source.get(name); if (value == null) { return null; } try { return Integer.parseInt(value); } catch (Exception e) { return null; } } @Override public Integer getInt(String name, int defValue) { Integer anInt = getInt(name); if (anInt == null) { return defValue; } return anInt; } public Boolean getBooleanNullForUnset(String name) { String value = this.source.get(name); if (value == null) { return null; } return value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("true"); } public Boolean getBoolean(String name) { String value = this.source.get(name); if (value == null) { return false; } return value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("true"); } @Override public Boolean getBoolean(String name, boolean defValue) { Boolean aBoolean = getBooleanNullForUnset(name); if (aBoolean == null) { return defValue; } return aBoolean; } public Double getDouble(String name) { String value = this.source.get(name); if (value == null) { return null; } try { return Double.parseDouble(value); } catch (Exception e) { return null; } } @Override public Double getDouble(String name, double defValue) { Double aDouble = getDouble(name); if (aDouble == null) { return defValue; } return aDouble; } public Long getLong(String name) { String value = this.source.get(name); if (value == null) { return null; } try { return Long.parseLong(value); } catch (Exception e) { return null; } } @Override public Long getLong(String name, long defValue) { Long aLong = getLong(name); if (aLong == null) { return defValue; } return aLong; } public List getStringList(String name) { String value = this.source.get(name); if (value == null) { return Collections.emptyList(); } return Arrays.stream(value.split(",")).filter(Objects::nonNull).collect(Collectors.toList()); } @Override public Runnable addChangeListener(ConfigChangeListener listener) { return notifier.addChangeListener(listener); } @Override public Set keySet() { return this.source.keySet(); } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/GlobalConfigs.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.config; import com.megaease.easeagent.config.report.ReportConfigAdapter; import com.megaease.easeagent.plugin.api.config.ConfigConst; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; public class GlobalConfigs extends Configs implements ConfigManagerMXBean { Configs originalConfig; public GlobalConfigs(Map source) { super(); this.originalConfig = new Configs(source); // reporter adapter Map map = new TreeMap<>(source); ReportConfigAdapter.convertConfig(map); // check environment config this.source = new TreeMap<>(map); this.notifier = new ConfigNotifier(""); } public Configs getOriginalConfig() { return this.originalConfig; } @Override public void updateConfigsNotNotify(Map changes) { // update original config Map newGlobalCfg = new TreeMap<>(this.originalConfig.getConfigs()); newGlobalCfg.putAll(changes); this.originalConfig.updateConfigsNotNotify(changes); // report adapter ReportConfigAdapter.convertConfig(newGlobalCfg); super.updateConfigsNotNotify(newGlobalCfg); } @Override public void updateConfigs(Map changes) { // update original config Map newGlobalCfg = new TreeMap<>(this.originalConfig.getConfigs()); newGlobalCfg.putAll(changes); this.originalConfig.updateConfigsNotNotify(changes); // report adapter ReportConfigAdapter.convertConfig(newGlobalCfg); super.updateConfigs(newGlobalCfg); } public void mergeConfigs(GlobalConfigs configs) { Map merged = configs.getOriginalConfig().getConfigs(); if (merged.isEmpty()) { return; } this.updateConfigsNotNotify(merged); return; } @Override public List availableConfigNames() { throw new UnsupportedOperationException(); } @Override public void updateService(String json, String version) throws IOException { this.updateConfigs(ConfigUtils.json2KVMap(json)); } @Override public void updateCanary(String json, String version) throws IOException { Map originals = ConfigUtils.json2KVMap(json); HashMap rst = new HashMap<>(); originals.forEach((k, v) -> rst.put(ConfigConst.join(ConfigConst.GLOBAL_CANARY_LABELS, k), v)); this.updateConfigs(rst); } @Override public void updateService2(Map configs, String version) { this.updateConfigs(configs); } @Override public void updateCanary2(Map configs, String version) { HashMap rst = new HashMap<>(); for (Map.Entry entry : configs.entrySet()) { String k = entry.getKey(); String v = entry.getValue(); rst.put(ConfigConst.join(ConfigConst.GLOBAL_CANARY_LABELS, k), v); } this.updateConfigs(CompatibilityConversion.transform(rst)); } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/JarFileConfigLoader.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.api.config.ConfigConst; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.jar.JarFile; import java.util.zip.ZipEntry; public class JarFileConfigLoader { private static final Logger LOGGER = LoggerFactory.getLogger(JarFileConfigLoader.class); static GlobalConfigs load(String file) { String agentJarPath = System.getProperty(ConfigConst.AGENT_JAR_PATH); if (agentJarPath == null) { return null; } try { JarFile jarFile = new JarFile(new File(agentJarPath)); ZipEntry zipEntry = jarFile.getEntry(file); if (zipEntry == null) { return null; } try (InputStream in = jarFile.getInputStream(zipEntry)) { return ConfigLoader.loadFromStream(in, file); } catch (IOException e) { LOGGER.debug("Load config file:{} failure: {}", file, e); } } catch (IOException e) { LOGGER.debug("create JarFile:{} failure: {}", agentJarPath, e); } return null; } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/OtelSdkConfigs.java ================================================ /* * Copyright (c) 2022, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.google.common.base.Splitter; import com.megaease.easeagent.plugin.utils.ImmutableMap; import com.megaease.easeagent.plugin.utils.SystemEnv; import com.megaease.easeagent.plugin.utils.common.StringUtils; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; /** * Compatible with opentelemetry-java. *

* {@see https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#disabling-opentelemetrysdk} */ public class OtelSdkConfigs { private static final String OTEL_RESOURCE_ATTRIBUTES_KEY = "otel.resource.attributes"; private static final String CONFIG_PATH_PROP_KEY = "otel.javaagent.configuration-file"; private static final Splitter.MapSplitter OTEL_RESOURCE_ATTRIBUTES_SPLITTER = Splitter.on(",") .omitEmptyStrings() .withKeyValueSeparator("="); private static final Map SDK_ATTRIBUTES_TO_EASE_AGENT_PROPS = ImmutableMap.builder() .put("sdk.disabled", "easeagent.server.enabled") .put("service.name", "name") //"easeagent.name" .put("service.namespace", "system") //"easeagent.system" .build(); // -Dotel.service.name=xxx private static final Map OTEL_SDK_PROPS_TO_EASE_AGENT_PROPS = new HashMap<>(); // OTEL_SERVICE_NAME=xxx private static final Map OTEL_SDK_ENV_VAR_TO_EASE_AGENT_PROPS = new HashMap<>(); static { for (Map.Entry entry : SDK_ATTRIBUTES_TO_EASE_AGENT_PROPS.entrySet()) { // lower.hyphen -> UPPER_UNDERSCORE OTEL_SDK_PROPS_TO_EASE_AGENT_PROPS.put( "otel." + entry.getKey(), entry.getValue() ); } for (Map.Entry entry : OTEL_SDK_PROPS_TO_EASE_AGENT_PROPS.entrySet()) { // dot.case -> UPPER_UNDERSCORE OTEL_SDK_ENV_VAR_TO_EASE_AGENT_PROPS.put( ConfigPropertiesUtils.toEnvVarName(entry.getKey()), entry.getValue() ); } } /** * Get config path from java properties or environment variables */ static String getConfigPath() { return ConfigPropertiesUtils.getString(CONFIG_PATH_PROP_KEY); } /** * update config value from environment variables and java properties *

* java properties > environment variables > OTEL_RESOURCE_ATTRIBUTES */ static Map updateEnvCfg() { Map envCfg = new TreeMap<>(); String configEnv = ConfigPropertiesUtils.getString(OTEL_RESOURCE_ATTRIBUTES_KEY); if (StringUtils.isNotEmpty(configEnv)) { Map map = OTEL_RESOURCE_ATTRIBUTES_SPLITTER.split(configEnv); if (!map.isEmpty()) { for (Map.Entry entry : SDK_ATTRIBUTES_TO_EASE_AGENT_PROPS.entrySet()) { String value = map.get(entry.getKey()); if (!StringUtils.isEmpty(value)) { envCfg.put(entry.getValue(), value); } } } } // override by environment variables, eg: export OTEL_SERVICE_NAME=xxx for (Map.Entry entry : OTEL_SDK_ENV_VAR_TO_EASE_AGENT_PROPS.entrySet()) { String value = SystemEnv.get(entry.getKey()); if (!StringUtils.isEmpty(value)) { envCfg.put(entry.getValue(), value); } } // override by java properties; eg: java -Dotel.service.name=xxx for (Map.Entry entry : OTEL_SDK_PROPS_TO_EASE_AGENT_PROPS.entrySet()) { String value = System.getProperty(entry.getKey()); if (!StringUtils.isEmpty(value)) { envCfg.put(entry.getValue(), value); } } return envCfg; } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/PluginConfig.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.api.config.Const; import com.megaease.easeagent.plugin.api.config.IPluginConfig; import com.megaease.easeagent.plugin.api.config.PluginConfigChangeListener; import com.megaease.easeagent.plugin.utils.common.StringUtils; import javax.annotation.Nonnull; import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; public class PluginConfig implements IPluginConfig { private static final Logger LOGGER = LoggerFactory.getLogger(PluginConfig.class); private final Set listeners; private final String domain; private final String namespace; private final String id; private final Map global; private final Map cover; private final boolean enabled; protected PluginConfig(@Nonnull String domain, @Nonnull String id, @Nonnull Map global, @Nonnull String namespace, @Nonnull Map cover, @Nonnull Set listeners) { this.domain = domain; this.namespace = namespace; this.id = id; this.global = global; this.cover = cover; this.listeners = listeners; Boolean b = getBoolean(Const.ENABLED_CONFIG); if (b == null) { enabled = false; } else { enabled = b; } } public static PluginConfig build(@Nonnull String domain, @Nonnull String id, @Nonnull Map global, @Nonnull String namespace, @Nonnull Map cover, PluginConfig oldConfig) { Set listeners; if (oldConfig == null) { listeners = new HashSet<>(); } else { listeners = oldConfig.listeners; } return new PluginConfig(domain, id, global, namespace, cover, listeners); } @Override public String domain() { return domain; } @Override public String namespace() { return namespace; } @Override public String id() { return id; } @Override public boolean hasProperty(String property) { return global.containsKey(property) || cover.containsKey(property); } @Override public String getString(String property) { String value = cover.get(property); if (value != null) { return value; } return global.get(property); } @Override public Integer getInt(String property) { String value = this.getString(property); if (value == null) { return null; } try { return Integer.parseInt(value); } catch (Exception e) { return null; } } private boolean isTrue(String value) { return value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("true"); } @Override public Boolean getBoolean(String property) { String value = cover.get(property); boolean implB = true; if (value != null) { implB = isTrue(value); } value = global.get(property); boolean globalB = false; if (value != null) { globalB = isTrue(value); } return implB && globalB; } @Override public boolean enabled() { return enabled; } @Override public Double getDouble(String property) { String value = this.getString(property); if (value == null) { return null; } try { return Double.parseDouble(value); } catch (Exception e) { return null; } } @Override public Long getLong(String property) { String value = this.getString(property); if (value == null) { return null; } try { return Long.parseLong(value); } catch (Exception e) { return null; } } @Override public List getStringList(String property) { String value = this.getString(property); if (StringUtils.isEmpty(value)) { return Collections.emptyList(); } return Arrays.stream(value.split(",")) .map(String::trim) .collect(Collectors.toList()); } @Override public IPluginConfig getGlobal() { return new Global(domain, id, global, namespace); } @Override public Set keySet() { Set keys = new HashSet<>(global.keySet()); keys.addAll(cover.keySet()); return keys; } @Override public void addChangeListener(PluginConfigChangeListener listener) { synchronized (listeners) { listeners.add(listener); } } public void foreachConfigChangeListener(Consumer action) { Set oldListeners; synchronized (listeners) { oldListeners = new HashSet<>(listeners); } for (PluginConfigChangeListener oldListener : oldListeners) { try { action.accept(oldListener); } catch (Exception e) { LOGGER.error("PluginConfigChangeListener<{}> change plugin config fail : {}", oldListener.getClass(), e.getMessage()); } } } public class Global extends PluginConfig implements IPluginConfig { public Global(String domain, String id, Map global, String namespace) { super(domain, id, global, namespace, Collections.emptyMap(), Collections.emptySet()); } @Override public void addChangeListener(PluginConfigChangeListener listener) { PluginConfig.this.addChangeListener(listener); } @Override public void foreachConfigChangeListener(Consumer action) { PluginConfig.this.foreachConfigChangeListener(action); } } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/PluginConfigManager.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.api.config.ChangeItem; import com.megaease.easeagent.plugin.api.config.Config; import com.megaease.easeagent.plugin.api.config.ConfigChangeListener; import com.megaease.easeagent.plugin.api.config.IConfigFactory; import java.util.*; import static com.megaease.easeagent.plugin.api.config.ConfigConst.PLUGIN_GLOBAL; public class PluginConfigManager implements IConfigFactory { private static final Logger LOGGER = LoggerFactory.getLogger(PluginConfigManager.class); private Runnable shutdownRunnable; private final Configs configs; private final Map pluginSourceConfigs; private final Map pluginConfigs; private PluginConfigManager(Configs configs, Map pluginSourceConfigs, Map pluginConfigs) { this.configs = Objects.requireNonNull(configs, "configs must not be null."); this.pluginSourceConfigs = Objects.requireNonNull(pluginSourceConfigs, "pluginSourceConfigs must not be null."); this.pluginConfigs = Objects.requireNonNull(pluginConfigs, "pluginConfigs must not be null."); } public static PluginConfigManager.Builder builder(Configs configs) { PluginConfigManager pluginConfigManager = new PluginConfigManager(configs, new HashMap<>(), new HashMap<>()); return pluginConfigManager.new Builder(); } @Override public Config getConfig() { return this.configs; } @Override public String getConfig(String property) { return configs.getString(property); } @Override public String getConfig(String property, String defaultValue) { return configs.getString(property, defaultValue); } public PluginConfig getConfig(String domain, String namespace, String id) { return getConfig(domain, namespace, id, null); } public synchronized PluginConfig getConfig(String domain, String namespace, String id, PluginConfig oldConfig) { Key key = new Key(domain, namespace, id); PluginConfig pluginConfig = pluginConfigs.get(key); if (pluginConfig != null) { return pluginConfig; } Map globalConfig = getGlobalConfig(domain, id); Map coverConfig = getCoverConfig(domain, namespace, id); PluginConfig newPluginConfig = PluginConfig.build(domain, id, globalConfig, namespace, coverConfig, oldConfig); pluginConfigs.put(key, newPluginConfig); return newPluginConfig; } private Map getGlobalConfig(String domain, String id) { return getConfigSource(domain, PLUGIN_GLOBAL, id); } private Map getCoverConfig(String domain, String namespace, String id) { return getConfigSource(domain, namespace, id); } private Map getConfigSource(String domain, String namespace, String id) { PluginSourceConfig sourceConfig = pluginSourceConfigs.get(new Key(domain, namespace, id)); if (sourceConfig == null) { return Collections.emptyMap(); } return sourceConfig.getProperties(); } private Set keys(Set keys) { Set propertyKeys = new HashSet<>(); for (String k : keys) { if (!ConfigUtils.isPluginConfig(k)) { continue; } PluginProperty property = ConfigUtils.pluginProperty(k); Key key = new Key(property.getDomain(), property.getNamespace(), property.getId()); propertyKeys.add(key); } return propertyKeys; } public void shutdown() { shutdownRunnable.run(); } protected synchronized void onChange(Map sources) { Set sourceKeys = keys(sources.keySet()); Map newSources = buildNewSources(sourceKeys, sources); for (Key sourceKey : sourceKeys) { pluginSourceConfigs.put(sourceKey, PluginSourceConfig.build(sourceKey.getDomain(), sourceKey.getNamespace(), sourceKey.getId(), newSources)); } Set changeKeys = buildChangeKeys(sourceKeys); for (Key changeKey : changeKeys) { final PluginConfig oldConfig = pluginConfigs.remove(changeKey); final PluginConfig newConfig = getConfig(changeKey.getDomain(), changeKey.getNamespace(), changeKey.id, oldConfig); if (oldConfig == null) { continue; } try { oldConfig.foreachConfigChangeListener(listener -> listener.onChange(oldConfig, newConfig)); } catch (Exception e) { LOGGER.warn("change config<{}> fail: {}", changeKey.toString(), e.getMessage()); } } } private Map buildNewSources(Set sourceKeys, Map sources) { Map newSources = new HashMap<>(); for (Key sourceKey : sourceKeys) { PluginSourceConfig pluginSourceConfig = pluginSourceConfigs.get(sourceKey); if (pluginSourceConfig == null) { continue; } newSources.putAll(pluginSourceConfig.getSource()); } newSources.putAll(sources); return newSources; } private Set buildChangeKeys(Set sourceKeys) { Set changeKeys = new HashSet<>(sourceKeys); for (Key key : sourceKeys) { if (!ConfigUtils.isGlobal(key.getNamespace())) { continue; } for (Key oldKey : pluginConfigs.keySet()) { if (!key.id.equals(oldKey.id)) { continue; } changeKeys.add(oldKey); } } return changeKeys; } class Key { private final String domain; private final String namespace; private final String id; public Key(String domain, String namespace, String id) { this.domain = domain; this.namespace = namespace; this.id = id; } public String getDomain() { return domain; } public String getNamespace() { return namespace; } public String getId() { return id; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Key key = (Key) o; return Objects.equals(domain, key.domain) && Objects.equals(namespace, key.namespace) && Objects.equals(id, key.id); } @Override public int hashCode() { return Objects.hash(domain, namespace, id); } @Override public String toString() { return "Key{" + "domain='" + domain + '\'' + ", namespace='" + namespace + '\'' + ", id='" + id + '\'' + '}'; } } public class Builder { public PluginConfigManager build() { synchronized (PluginConfigManager.this) { Map sources = configs.getConfigs(); Set sourceKeys = keys(sources.keySet()); for (Key sourceKey : sourceKeys) { pluginSourceConfigs.put(sourceKey, PluginSourceConfig.build(sourceKey.getDomain(), sourceKey.getNamespace(), sourceKey.getId(), sources)); } for (Key key : pluginSourceConfigs.keySet()) { getConfig(key.getDomain(), key.getNamespace(), key.getId()); } shutdownRunnable = configs.addChangeListener(new ChangeListener()); } return PluginConfigManager.this; } } class ChangeListener implements ConfigChangeListener { @Override public void onChange(List list) { Map sources = new HashMap<>(); for (ChangeItem changeItem : list) { sources.put(changeItem.getFullName(), changeItem.getNewValue()); } PluginConfigManager.this.onChange(sources); } } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/PluginProperty.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import java.util.Objects; public class PluginProperty { private final String domain; private final String namespace; private final String id; private final String property; public PluginProperty(String domain, String namespace, String id, String property) { this.domain = domain; this.namespace = namespace; this.id = id; this.property = property; } public String getDomain() { return domain; } public String getNamespace() { return namespace; } public String getId() { return id; } public String getProperty() { return property; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PluginProperty that = (PluginProperty) o; return Objects.equals(domain, that.domain) && Objects.equals(namespace, that.namespace) && Objects.equals(id, that.id) && Objects.equals(property, that.property); } @Override public int hashCode() { return Objects.hash(domain, namespace, id, property); } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/PluginSourceConfig.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import java.util.HashMap; import java.util.Map; import java.util.Objects; public class PluginSourceConfig { private final String domain; private final String namespace; private final String id; private final Map source; private final Map properties; public PluginSourceConfig(String domain, String namespace, String id, Map source, Map properties) { this.domain = Objects.requireNonNull(domain, "domain must not be null."); this.namespace = Objects.requireNonNull(namespace, "namespace must not be null."); this.id = Objects.requireNonNull(id, "id must not be null."); this.source = Objects.requireNonNull(source, "source must not be null."); this.properties = Objects.requireNonNull(properties, "properties must not be null."); } public static PluginSourceConfig build(String domain, String namespace, String id, Map source) { Map pluginSource = new HashMap<>(); Map properties = new HashMap<>(); for (Map.Entry sourceEntry : source.entrySet()) { String key = sourceEntry.getKey(); if (!ConfigUtils.isPluginConfig(key, domain, namespace, id)) { continue; } pluginSource.put(key, sourceEntry.getValue()); PluginProperty property = ConfigUtils.pluginProperty(key); properties.put(property, sourceEntry.getValue()); } return new PluginSourceConfig(domain, namespace, id, pluginSource, properties); } public Map getSource() { return source; } public String getDomain() { return domain; } public String getNamespace() { return namespace; } public String getId() { return id; } public Map getProperties() { Map result = new HashMap<>(); for (Map.Entry propertyEntry : properties.entrySet()) { result.put(propertyEntry.getKey().getProperty(), propertyEntry.getValue()); } return result; } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/ValidateUtils.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; public class ValidateUtils { public static class ValidException extends RuntimeException { public ValidException(String message) { super(message); } } public interface Validator { void validate(String name, String value); } public static void validate(Configs configs, String name, Validator... vs){ String value = configs.getString(name); for (Validator one : vs) { one.validate(name, value); } } public static final Validator HasText = (name, value) -> { if (value == null || value.trim().length() == 0) { throw new ValidException(String.format("Property[%s] has no non-empty value", name)); } }; public static final Validator Bool = (name, value) -> { String upper = value.toUpperCase(); if (upper.equals("TRUE") || upper.equals("FALSE")) { return; } throw new ValidException(String.format("Property[%s] has no boolean value", name)); }; public static final Validator NumberInt = (name, value) -> { try { Integer.parseInt(value.trim()); } catch (Exception e) { throw new ValidException(String.format("Property[%s] has no integer value", name)); } }; } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/WrappedConfigManager.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.async.ThreadUtils; import java.io.IOException; import java.util.List; import java.util.Map; public class WrappedConfigManager implements ConfigManagerMXBean { private final ClassLoader customClassLoader; private final ConfigManagerMXBean conf; public WrappedConfigManager(ClassLoader customClassLoader, ConfigManagerMXBean config) { this.customClassLoader = customClassLoader; this.conf = config; } @Override public void updateConfigs(Map configs) { ThreadUtils.callWithClassLoader(customClassLoader, () -> { conf.updateConfigs(configs); return null; }); } @Override public void updateService(String json, String version) throws IOException { try { ThreadUtils.callWithClassLoader(customClassLoader, () -> { try { conf.updateService(json, version); } catch (IOException e) { throw new RuntimeException(e); } return null; }); } catch (RuntimeException e) { if (e.getCause() instanceof IOException) { throw (IOException) e.getCause(); } throw e; } } @Override public void updateCanary(String json, String version) throws IOException { try { ThreadUtils.callWithClassLoader(customClassLoader, () -> { try { conf.updateCanary(json, version); } catch (IOException e) { throw new RuntimeException(e); } return null; }); } catch (RuntimeException e) { if (e.getCause() instanceof IOException) { throw (IOException) e.getCause(); } throw e; } } @Override public void updateService2(Map configs, String version) { ThreadUtils.callWithClassLoader(customClassLoader, () -> { conf.updateService2(configs, version); return null; }); } @Override public void updateCanary2(Map configs, String version) { ThreadUtils.callWithClassLoader(customClassLoader, () -> { conf.updateCanary2(configs, version); return null; }); } @Override public Map getConfigs() { return ThreadUtils.callWithClassLoader(customClassLoader, conf::getConfigs); } @Override public List availableConfigNames() { return ThreadUtils.callWithClassLoader(customClassLoader, conf::availableConfigNames); } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/report/ReportConfigAdapter.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.config.report; import com.megaease.easeagent.plugin.api.config.Config; import com.megaease.easeagent.plugin.api.config.ConfigConst; import com.megaease.easeagent.plugin.api.config.Const; import com.megaease.easeagent.plugin.utils.NoNull; import com.megaease.easeagent.plugin.utils.common.StringUtils; import lombok.extern.slf4j.Slf4j; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.TreeMap; import static com.megaease.easeagent.config.ConfigUtils.extractAndConvertPrefix; import static com.megaease.easeagent.config.ConfigUtils.extractByPrefix; import static com.megaease.easeagent.config.report.ReportConfigConst.*; @Slf4j public class ReportConfigAdapter { private ReportConfigAdapter() {} public static void convertConfig(Map config) { Map cfg = extractAndConvertReporterConfig(config); config.putAll(cfg); } public static Map extractReporterConfig(Config configs) { Map cfg = extractByPrefix(configs.getConfigs(), REPORT); // default config cfg.put(TRACE_ENCODER, NoNull.of(cfg.get(TRACE_ENCODER), SPAN_JSON_ENCODER_NAME)); cfg.put(METRIC_ENCODER, NoNull.of(cfg.get(METRIC_ENCODER), METRIC_JSON_ENCODER_NAME)); cfg.put(LOG_ENCODER, NoNull.of(cfg.get(LOG_ENCODER), LOG_DATA_JSON_ENCODER_NAME)); cfg.put(LOG_ACCESS_ENCODER, NoNull.of(cfg.get(LOG_ACCESS_ENCODER), ACCESS_LOG_JSON_ENCODER_NAME)); cfg.put(TRACE_SENDER_NAME, NoNull.of(cfg.get(TRACE_SENDER_NAME), getDefaultAppender(cfg))); cfg.put(METRIC_SENDER_NAME, NoNull.of(cfg.get(METRIC_SENDER_NAME), getDefaultAppender(cfg))); cfg.put(LOG_ACCESS_SENDER_NAME, NoNull.of(cfg.get(LOG_ACCESS_SENDER_NAME), getDefaultAppender(cfg))); cfg.put(LOG_SENDER_NAME, NoNull.of(cfg.get(LOG_SENDER_NAME), getDefaultAppender(cfg))); return cfg; } public static String getDefaultAppender(Map cfg) { String outputAppender = cfg.get(join(OUTPUT_SERVER_V2, APPEND_TYPE_KEY)); if (StringUtils.isEmpty(outputAppender)) { return Const.DEFAULT_APPEND_TYPE; } return outputAppender; } private static Map extractAndConvertReporterConfig(Map srcConfig) { Map extract = extractTracingConfig(srcConfig); Map outputCfg = new TreeMap<>(extract); // metric config extract = extractMetricPluginConfig(srcConfig); outputCfg.putAll(extract); // log config extract = extractLogPluginConfig(srcConfig); outputCfg.putAll(extract); // if there are access log in metric updateAccessLogCfg(outputCfg); // all extract configuration will be overridden by config items start with "report" in srcConfig extract = extractByPrefix(srcConfig, REPORT); outputCfg.putAll(extract); return outputCfg; } /** * this can be deleted if there is not any v1 configuration needed to compatible with * convert v1 tracing config to v2 */ private static Map extractTracingConfig(Map srcCfg) { // outputServer config Map extract = extractAndConvertPrefix(srcCfg, OUTPUT_SERVER_V1, OUTPUT_SERVER_V2); Map outputCfg = new TreeMap<>(extract); // async output config extract = extractAndConvertPrefix(srcCfg, TRACE_OUTPUT_V1, TRACE_ASYNC); String target = srcCfg.get(join(TRACE_OUTPUT_V1, "target")); extract.remove(join(TRACE_ASYNC, "target")); if (!StringUtils.isEmpty(outputCfg.get(TRACE_SENDER_NAME))) { log.info("Reporter V2 config trace sender as: {}", outputCfg.get(TRACE_SENDER_NAME)); } else if ("system".equals(target)) { // check output servers if (StringUtils.hasText(outputCfg.get(BOOTSTRAP_SERVERS))) { outputCfg.put(TRACE_SENDER_NAME, KAFKA_SENDER_NAME); outputCfg.put(TRACE_SENDER_TOPIC_V2, extract.remove(join(TRACE_ASYNC, TOPIC_KEY))); } else { outputCfg.put(TRACE_SENDER_NAME, CONSOLE_SENDER_NAME); } } else if ("zipkin".equals(target)) { outputCfg.put(TRACE_SENDER_NAME, ZIPKIN_SENDER_NAME); String url = extract.remove(join(TRACE_ASYNC, "target.zipkinUrl")); if (StringUtils.isEmpty(url)) { outputCfg.put(TRACE_SENDER_NAME, CONSOLE_SENDER_NAME); } else { outputCfg.put(join(TRACE_SENDER, "url"), url); } } else if (!StringUtils.isEmpty(target)) { outputCfg.put(TRACE_SENDER_NAME, CONSOLE_SENDER_NAME); log.info("Unsupported output configuration item:{}={}", TRACE_OUTPUT_TARGET_V1, target); } outputCfg.putAll(extract); return outputCfg; } /** * For Compatibility, call after metric config adapter * * extract 'reporter.metric.access.*' to 'reporter.log.access.*' */ private static void updateAccessLogCfg(Map outputCfg) { // reporter.metric.access.* String prefix = join(METRIC_V2, ConfigConst.Namespace.ACCESS); Map metricAccess = extractByPrefix(outputCfg, prefix); Map accessLog = extractAndConvertPrefix(metricAccess, prefix, LOG_ACCESS); // access log use `kafka` sender if (METRIC_KAFKA_SENDER_NAME.equals(accessLog.get(LOG_ACCESS_SENDER_NAME))) { accessLog.put(LOG_ACCESS_SENDER_NAME, KAFKA_SENDER_NAME); } outputCfg.putAll(accessLog); } /** * metric report configuration * * extract `plugin.observability.global.metric.*` config items to reporter.metric.sender.*` * * extract `plugin.observability.[namespace].metric.*` config items * to reporter.metric.[namespace].sender.*` * * @param srcCfg source configuration map * @return metric reporter config start with 'reporter.metric.[namespace].sender' */ private static Map extractMetricPluginConfig(Map srcCfg) { final String globalKey = "." + ConfigConst.PLUGIN_GLOBAL + "."; final String prefix = join(ConfigConst.PLUGIN, ConfigConst.OBSERVABILITY); int metricKeyLength = ConfigConst.METRIC_SERVICE_ID.length(); Map global = extractGlobalMetricConfig(srcCfg); HashSet namespaces = new HashSet<>(); Map metricConfigs = new HashMap<>(global); for (Map.Entry e : srcCfg.entrySet()) { String key = e.getKey(); if (!key.startsWith(prefix)) { continue; } int idx = key.indexOf(ConfigConst.METRIC_SERVICE_ID, prefix.length()); if (idx < 0) { continue; } String namespaceWithSeparator = key.substring(prefix.length(), idx); String suffix = key.substring(idx + metricKeyLength + 1); String newKey; if (namespaceWithSeparator.equals(globalKey)) { continue; } else { if (!namespaces.contains(namespaceWithSeparator)) { namespaces.add(namespaceWithSeparator); Map d = extractAndConvertPrefix(global, METRIC_V2 + ".", METRIC_V2 + namespaceWithSeparator); metricConfigs.putAll(d); } } if (suffix.startsWith(ENCODER_KEY) || suffix.startsWith(ASYNC_KEY)) { newKey = METRIC_V2 + namespaceWithSeparator + suffix; } else if (suffix.equals(INTERVAL_KEY)) { newKey = METRIC_V2 + namespaceWithSeparator + join(ASYNC_KEY, suffix); } else { newKey = METRIC_V2 + namespaceWithSeparator + join(SENDER_KEY, suffix); } if (newKey.endsWith(APPEND_TYPE_KEY) && e.getValue().equals("kafka")) { metricConfigs.put(newKey, METRIC_KAFKA_SENDER_NAME); } else { metricConfigs.put(newKey, e.getValue()); } } return metricConfigs; } private static Map extractGlobalMetricConfig(Map srcCfg) { final String prefix = join(ConfigConst.PLUGIN, ConfigConst.OBSERVABILITY, ConfigConst.PLUGIN_GLOBAL, ConfigConst.PluginID.METRIC); Map global = new TreeMap<>(); Map extract = extractAndConvertPrefix(srcCfg, prefix, METRIC_SENDER); for (Map.Entry e : extract.entrySet()) { if (e.getKey().startsWith(ENCODER_KEY, METRIC_SENDER.length() + 1)) { global.put(join(METRIC_V2, e.getKey().substring(METRIC_SENDER.length() + 1)), e.getValue()); } else if (e.getKey().endsWith(INTERVAL_KEY)) { global.put(join(METRIC_ASYNC, INTERVAL_KEY), e.getValue()); } else if (e.getKey().endsWith(APPEND_TYPE_KEY) && e.getValue().equals("kafka")) { global.put(e.getKey(), METRIC_KAFKA_SENDER_NAME); } else { global.put(e.getKey(), e.getValue()); } } // global log level (async) global.putAll(extractByPrefix(srcCfg, METRIC_SENDER)); global.putAll(extractByPrefix(srcCfg, METRIC_ASYNC)); global.putAll(extractByPrefix(srcCfg, METRIC_ENCODER)); return global; } /** * metric report configuration * * extract `plugin.observability.global.metric.*` config items to reporter.metric.sender.*` * * extract `plugin.observability.[namespace].metric.*` config items * to reporter.metric.[namespace].sender.*` * * @param srcCfg source configuration map * @return metric reporter config start with 'reporter.metric.[namespace].sender' */ private static Map extractLogPluginConfig(Map srcCfg) { final String globalKey = "." + ConfigConst.PLUGIN_GLOBAL + "."; final String prefix = join(ConfigConst.PLUGIN, ConfigConst.OBSERVABILITY); String typeKey = join("", ConfigConst.PluginID.LOG, ""); int typeKeyLength = ConfigConst.PluginID.LOG.length(); final String reporterPrefix = LOGS; Map global = extractGlobalLogConfig(srcCfg); HashSet namespaces = new HashSet<>(); Map outputConfigs = new TreeMap<>(global); for (Map.Entry e : srcCfg.entrySet()) { String key = e.getKey(); if (!key.startsWith(prefix)) { continue; } int idx = key.indexOf(typeKey, prefix.length()); if (idx < 0) { continue; } else { idx += 1; } String namespaceWithSeparator = key.substring(prefix.length(), idx); String suffix = key.substring(idx + typeKeyLength + 1); String newKey; if (namespaceWithSeparator.equals(globalKey)) { continue; } else { if (!namespaces.contains(namespaceWithSeparator)) { namespaces.add(namespaceWithSeparator); Map d = extractAndConvertPrefix(global, reporterPrefix + ".", reporterPrefix + namespaceWithSeparator); outputConfigs.putAll(d); } } if (suffix.startsWith(ENCODER_KEY) || suffix.startsWith(ASYNC_KEY)) { newKey = reporterPrefix + namespaceWithSeparator + suffix; } else { newKey = reporterPrefix + namespaceWithSeparator + join(SENDER_KEY, suffix); } outputConfigs.put(newKey, e.getValue()); } return outputConfigs; } /** * extract `plugin.observability.global.log.*` config items to `reporter.log.sender.*` * extract `plugin.observability.global.log.output.*` config items to `reporter.log.output.*` * * @param srcCfg source config map * @return reporter log config */ private static Map extractGlobalLogConfig(Map srcCfg) { final String prefix = join(ConfigConst.PLUGIN, ConfigConst.OBSERVABILITY, ConfigConst.PLUGIN_GLOBAL, ConfigConst.PluginID.LOG); Map global = new TreeMap<>(); Map extract = extractAndConvertPrefix(srcCfg, prefix, LOG_SENDER); for (Map.Entry e : extract.entrySet()) { String key = e.getKey(); if (key.startsWith(ENCODER_KEY, LOG_SENDER.length() + 1)) { global.put(join(LOGS, key.substring(LOG_SENDER.length() + 1)), e.getValue()); } else if (key.startsWith(ASYNC_KEY, LOG_SENDER.length() + 1)) { global.put(join(LOGS, key.substring(LOG_SENDER.length() + 1)), e.getValue()); } else { global.put(e.getKey(), e.getValue()); } } // global log level (async) global.putAll(extractByPrefix(srcCfg, LOG_SENDER)); global.putAll(extractByPrefix(srcCfg, LOG_ASYNC)); global.putAll(extractByPrefix(srcCfg, LOG_ENCODER)); return global; } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/report/ReportConfigConst.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.config.report; @SuppressWarnings("unused") public class ReportConfigConst { private ReportConfigConst() {} public static final String KAFKA_SENDER_NAME = "kafka"; public static final String METRIC_KAFKA_SENDER_NAME = "metricKafka"; public static final String CONSOLE_SENDER_NAME = "console"; public static final String ZIPKIN_SENDER_NAME = "http"; public static final String NOOP_SENDER_NAME = "noop"; public static final String SPAN_JSON_ENCODER_NAME = "SpanJsonEncoder"; public static final String METRIC_JSON_ENCODER_NAME = "MetricJsonEncoder"; public static final String LOG_DATA_JSON_ENCODER_NAME = "LogDataJsonEncoder"; public static final String ACCESS_LOG_JSON_ENCODER_NAME = "AccessLogJsonEncoder"; public static final String HTTP_SPAN_JSON_ENCODER_NAME = "HttpSpanJsonEncoder"; public static final String LOG_ENCODER_NAME = "StringEncoder"; static final String DELIMITER = "."; public static final String TOPIC_KEY = "topic"; public static final String LOG_APPENDER_KEY = "appenderName"; public static final String ENABLED_KEY = "enabled"; public static final String SENDER_KEY = "sender"; public static final String ENCODER_KEY = "encoder"; public static final String ASYNC_KEY = "output"; public static final String APPEND_TYPE_KEY = "appendType"; public static final String INTERVAL_KEY = "interval"; public static final String ASYNC_THREAD_KEY = "reportThread"; public static final String ASYNC_MSG_MAX_BYTES_KEY = "messageMaxBytes"; public static final String ASYNC_MSG_TIMEOUT_KEY = "messageTimeout"; public static final String ASYNC_QUEUE_MAX_SIZE_KEY = "queuedMaxSize"; public static final String ASYNC_QUEUE_MAX_LOGS_KEY = "queuedMaxLogs"; public static final String ASYNC_QUEUE_MAX_ITEMS_KEY = "queuedMaxItems"; /** * Reporter v2 configuration */ // -- lv1 -- public static final String REPORT = "reporter"; // ---- lv2 ---- public static final String OUTPUT_SERVER_V2 = join(REPORT, "outputServer"); public static final String TRACE_V2 = join(REPORT, "tracing"); public static final String LOGS = join(REPORT, "log"); public static final String METRIC_V2 = join(REPORT, "metric"); public static final String GENERAL = join(REPORT, "general"); // ------ lv3 ------ public static final String BOOTSTRAP_SERVERS = join(OUTPUT_SERVER_V2, "bootstrapServer"); public static final String OUTPUT_SERVERS_ENABLE = join(OUTPUT_SERVER_V2, ENABLED_KEY); public static final String OUTPUT_SERVERS_TIMEOUT = join(OUTPUT_SERVER_V2, "timeout"); public static final String OUTPUT_SECURITY_PROTOCOL_V2 = join(OUTPUT_SERVER_V2, "security.protocol"); public static final String OUTPUT_SERVERS_SSL = join(OUTPUT_SERVER_V2, "ssl"); public static final String LOG_ASYNC = join(LOGS, ASYNC_KEY); public static final String LOG_SENDER = join(LOGS, SENDER_KEY); public static final String LOG_ENCODER = join(LOGS, ENCODER_KEY); public static final String LOG_ACCESS = join(LOGS, "access"); public static final String LOG_ACCESS_SENDER = join(LOG_ACCESS, SENDER_KEY); public static final String LOG_ACCESS_ENCODER = join(LOG_ACCESS, ENCODER_KEY); public static final String TRACE_SENDER = join(TRACE_V2, SENDER_KEY); public static final String TRACE_ENCODER = join(TRACE_V2, ENCODER_KEY); public static final String TRACE_ASYNC = join(TRACE_V2, ASYNC_KEY); public static final String METRIC_SENDER = join(METRIC_V2, SENDER_KEY); public static final String METRIC_ENCODER = join(METRIC_V2, ENCODER_KEY); public static final String METRIC_ASYNC = join(METRIC_V2, ASYNC_KEY); // -------- lv4 -------- public static final String LOG_SENDER_TOPIC = join(LOG_SENDER, TOPIC_KEY); public static final String LOG_SENDER_NAME = join(LOG_SENDER, APPEND_TYPE_KEY); public static final String LOG_ACCESS_SENDER_NAME = join(LOG_ACCESS_SENDER, APPEND_TYPE_KEY); public static final String LOG_ACCESS_SENDER_ENABLED = join(LOG_ACCESS_SENDER, ENABLED_KEY); public static final String LOG_ACCESS_SENDER_TOPIC = join(LOG_ACCESS_SENDER, TOPIC_KEY); public static final String LOG_ASYNC_MESSAGE_MAX_BYTES = join(LOG_ASYNC, ASYNC_MSG_MAX_BYTES_KEY); public static final String LOG_ASYNC_REPORT_THREAD = join(LOG_ASYNC, ASYNC_THREAD_KEY); public static final String LOG_ASYNC_MESSAGE_TIMEOUT = join(LOG_ASYNC, ASYNC_MSG_TIMEOUT_KEY); public static final String LOG_ASYNC_QUEUED_MAX_LOGS = join(LOG_ASYNC, ASYNC_QUEUE_MAX_LOGS_KEY); public static final String LOG_ASYNC_QUEUED_MAX_SIZE = join(LOG_ASYNC, ASYNC_QUEUE_MAX_SIZE_KEY); public static final String TRACE_SENDER_NAME = join(TRACE_SENDER, APPEND_TYPE_KEY); public static final String TRACE_SENDER_ENABLED_V2 = join(TRACE_SENDER, ENABLED_KEY); public static final String TRACE_SENDER_TOPIC_V2 = join(TRACE_SENDER, TOPIC_KEY); public static final String TRACE_ASYNC_MESSAGE_MAX_BYTES_V2 = join(TRACE_ASYNC, ASYNC_MSG_MAX_BYTES_KEY); public static final String TRACE_ASYNC_REPORT_THREAD_V2 = join(TRACE_ASYNC, ASYNC_THREAD_KEY); public static final String TRACE_ASYNC_MESSAGE_TIMEOUT_V2 = join(TRACE_ASYNC, ASYNC_MSG_TIMEOUT_KEY); public static final String TRACE_ASYNC_QUEUED_MAX_SPANS_V2 = join(TRACE_ASYNC, "queuedMaxSpans"); public static final String TRACE_ASYNC_QUEUED_MAX_SIZE_V2 = join(TRACE_ASYNC, ASYNC_QUEUE_MAX_SIZE_KEY); public static final String METRIC_SENDER_NAME = join(METRIC_SENDER, APPEND_TYPE_KEY); public static final String METRIC_SENDER_ENABLED = join(METRIC_SENDER, ENABLED_KEY); public static final String METRIC_SENDER_TOPIC = join(METRIC_SENDER, TOPIC_KEY); public static final String METRIC_SENDER_APPENDER = join(METRIC_SENDER, LOG_APPENDER_KEY); public static final String METRIC_ASYNC_INTERVAL = join(METRIC_ASYNC, INTERVAL_KEY); public static final String METRIC_ASYNC_QUEUED_MAX_ITEMS = join(METRIC_ASYNC, ASYNC_QUEUE_MAX_ITEMS_KEY); public static final String METRIC_ASYNC_MESSAGE_MAX_BYTES = join(METRIC_ASYNC, ASYNC_MSG_MAX_BYTES_KEY); public static final String OUTPUT_SSL_KEYSTORE_TYPE_V2 = join(OUTPUT_SERVERS_SSL, "keystore.type"); public static final String OUTPUT_KEY_V2 = join(OUTPUT_SERVERS_SSL, "keystore.key"); public static final String OUTPUT_CERT_V2 = join(OUTPUT_SERVERS_SSL, "keystore.certificate.chain"); public static final String OUTPUT_TRUST_CERT_V2 = join(OUTPUT_SERVERS_SSL, "truststore.certificates"); public static final String OUTPUT_TRUST_CERT_TYPE_V2 = join(OUTPUT_SERVERS_SSL, "truststore.type"); public static final String OUTPUT_ENDPOINT_IDENTIFICATION_ALGORITHM_V2 = join(OUTPUT_SERVERS_SSL, "endpoint.identification.algorithm"); /** * Reporter v1 configuration */ public static final String OBSERVABILITY = "observability"; // ---- lv2 ---- public static final String TRACING = join(OBSERVABILITY, "tracings"); public static final String OUTPUT_SERVER_V1 = join(OBSERVABILITY, "outputServer"); // ------ lv3 ------ public static final String TRACE_OUTPUT_V1 = join(TRACING, ASYNC_KEY); public static final String BOOTSTRAP_SERVERS_V1 = join(OUTPUT_SERVER_V1, "bootstrapServer"); // --------- lv4 --------- public static final String TRACE_OUTPUT_ENABLED_V1 = join(TRACE_OUTPUT_V1, ENABLED_KEY); public static final String TRACE_OUTPUT_TOPIC_V1 = join(TRACE_OUTPUT_V1, TOPIC_KEY); public static final String TRACE_OUTPUT_TARGET_V1 = join(TRACE_OUTPUT_V1, "target"); public static final String TRACE_OUTPUT_TARGET_ZIPKIN_URL = join(TRACE_OUTPUT_V1, "target.zipkinUrl"); public static final String TRACE_OUTPUT_REPORT_THREAD_V1 = join(TRACE_OUTPUT_V1, ASYNC_THREAD_KEY); public static final String TRACE_OUTPUT_MESSAGE_TIMEOUT_V1 = join(TRACE_OUTPUT_V1, ASYNC_MSG_TIMEOUT_KEY); public static final String TRACE_OUTPUT_QUEUED_MAX_SPANS_V1 = join(TRACE_OUTPUT_V1, "queuedMaxSpans"); public static final String TRACE_OUTPUT_QUEUED_MAX_SIZE_V1 = join(TRACE_OUTPUT_V1, ASYNC_QUEUE_MAX_SIZE_KEY); public static final String GLOBAL_METRIC = "plugin.observability.global.metric"; public static final String GLOBAL_METRIC_ENABLED = join(GLOBAL_METRIC, ENABLED_KEY); public static final String GLOBAL_METRIC_TOPIC = join(GLOBAL_METRIC, TOPIC_KEY); public static final String GLOBAL_METRIC_APPENDER = join(GLOBAL_METRIC, APPEND_TYPE_KEY); public static String join(String... texts) { return String.join(DELIMITER, texts); } } ================================================ FILE: config/src/main/java/com/megaease/easeagent/config/yaml/YamlReader.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config.yaml; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import java.io.InputStream; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; public class YamlReader { private Map yaml; private static final DumperOptions DUMPER_OPTIONS; static { DUMPER_OPTIONS = new DumperOptions(); DUMPER_OPTIONS.setLineBreak(DumperOptions.LineBreak.getPlatformLineBreak()); } public YamlReader() { // ignored } public YamlReader load(InputStream in) { if (in != null) { yaml = new Yaml(DUMPER_OPTIONS).load(in); } return this; } public Map getYaml() { return yaml; } public static YamlReader merge(YamlReader target, YamlReader source) { Map targetMap = target.yaml; Map sourceMap = source.yaml; merge(targetMap, sourceMap); YamlReader result = new YamlReader(); result.yaml = new HashMap<>(targetMap); return result; } @SuppressWarnings("unchecked") private static void merge(Map target, Map source) { source.forEach((key, value) -> { Object existing = target.get(key); if (value instanceof Map && existing instanceof Map) { Map result = new LinkedHashMap<>((Map) existing); merge(result, (Map) value); target.put(key, result); } else { target.put(key, value); } }); } public Map compress() { if (Objects.isNull(yaml) || yaml.size() == 0) { return Collections.emptyMap(); } final Deque keyStack = new LinkedList<>(); final Map resultMap = new HashMap<>(); compress(yaml, keyStack, resultMap); return resultMap; } @SuppressWarnings("unchecked") private void compress(Map result, Deque keyStack, Map resultMap) { result.forEach((k, v) -> { keyStack.addLast(String.valueOf(k)); if (v instanceof Map) { compress((Map) v, keyStack, resultMap); keyStack.removeLast(); return; } if (v instanceof List) { String value = ((List) v).stream() .map(String::valueOf) .collect(Collectors.joining(",")); resultMap.put(String.join(".", keyStack), value); keyStack.removeLast(); return; } resultMap.put(String.join(".", keyStack), String.valueOf(v)); keyStack.removeLast(); }); } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/CompatibilityConversionTest.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.api.ProgressFields; import com.megaease.easeagent.plugin.api.config.ConfigConst; import org.junit.Test; import java.util.Collections; import java.util.Map; import static com.megaease.easeagent.config.CompatibilityConversion.REQUEST_NAMESPACE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class CompatibilityConversionTest { @Test public void transform() { Map newMap = CompatibilityConversion.transform(Collections.singletonMap(ConfigConst.Observability.METRICS_ENABLED, "false")); assertEquals(2, newMap.size()); assertTrue(newMap.containsKey(ConfigConst.Plugin.OBSERVABILITY_GLOBAL_METRIC_ENABLED)); assertTrue(newMap.containsKey(ConfigConst.Observability.METRICS_ENABLED)); newMap = CompatibilityConversion.transform(Collections.singletonMap(ConfigConst.Observability.TRACE_ENABLED, "false")); assertEquals(1, newMap.size()); assertTrue(newMap.containsKey(ConfigConst.Plugin.OBSERVABILITY_GLOBAL_TRACING_ENABLED)); // newMap = CompatibilityConversion.transform(Collections.singletonMap("globalCanaryHeaders.serviceHeaders.mesh-app-backend.0", "X-canary")); // assertEquals(1, newMap.size()); // assertTrue(newMap.containsKey(ProgressFields.EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG + ".mesh-app-backend.0")); newMap = CompatibilityConversion.transform(Collections.singletonMap("observability.outputServer.bootstrapServer", "tstatssta")); assertEquals(1, newMap.size()); assertTrue(newMap.containsKey("observability.outputServer.bootstrapServer")); newMap = CompatibilityConversion.transform(Collections.singletonMap("observability.metrics.access.enabled", "true")); assertEquals(1, newMap.size()); assertEquals("true", newMap.get("plugin.observability.access.metric.enabled")); newMap = CompatibilityConversion.transform(Collections.singletonMap("observability.metrics.access.interval", "30")); assertEquals(1, newMap.size()); assertEquals("30", newMap.get("plugin.observability.access.metric.interval")); newMap = CompatibilityConversion.transform(Collections.singletonMap("observability.tracings.remoteInvoke.enabled", "true")); assertEquals(1, newMap.size()); assertEquals("true", newMap.get("plugin.observability.webclient.tracing.enabled")); newMap = CompatibilityConversion.transform(Collections.singletonMap("observability.tracings.request.enabled", "true")); assertEquals(REQUEST_NAMESPACE.length, newMap.size()); for (String s : REQUEST_NAMESPACE) { assertEquals("true", newMap.get("plugin.observability." + s + ".tracing.enabled")); } newMap = CompatibilityConversion.transform(Collections.singletonMap("observability.metrics.jvmGc.enabled", "true")); assertEquals(1, newMap.size()); assertEquals("true", newMap.get("observability.metrics.jvmGc.enabled")); newMap = CompatibilityConversion.transform(Collections.singletonMap("observability.tracings.sampledType", "counting")); assertEquals(1, newMap.size()); assertEquals("counting", newMap.get("observability.tracings.sampledType")); newMap = CompatibilityConversion.transform(Collections.singletonMap("observability.tracings.sampled", "100")); assertEquals(1, newMap.size()); assertEquals("100", newMap.get("observability.tracings.sampled")); newMap = CompatibilityConversion.transform(Collections.singletonMap("observability.tracings.output.enabled", "true")); assertEquals(1, newMap.size()); assertEquals("true", newMap.get("observability.tracings.output.enabled")); newMap = CompatibilityConversion.transform(Collections.singletonMap("observability.metrics.jdbcConnection.interval", "30")); assertEquals(1, newMap.size()); assertEquals("30", newMap.get("plugin.observability.jdbcConnection.metric.interval")); newMap = CompatibilityConversion.transform(Collections.singletonMap("observability.metrics.aaaaaaaaaa.interval", "30")); assertEquals(1, newMap.size()); assertEquals("30", newMap.get("plugin.observability.aaaaaaaaaa.metric.interval")); } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/ConfigFactoryTest.java ================================================ /* * Copyright (c) 2022, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.utils.SystemEnv; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import java.io.File; import java.net.URISyntaxException; import static com.megaease.easeagent.config.ConfigFactory.AGENT_SERVICE; import static com.megaease.easeagent.config.ConfigFactory.AGENT_SYSTEM; import static org.junit.Assert.assertEquals; public class ConfigFactoryTest { @Test public void test_yaml() { Configs config = ConfigFactory.loadConfigs(null, this.getClass().getClassLoader()); assertEquals("test-service", config.getString(AGENT_SERVICE)); assertEquals("demo-system", config.getString(AGENT_SYSTEM)); } @Test public void test_env() { try (MockedStatic mock = Mockito.mockStatic(SystemEnv.class)) { mock.when(() -> SystemEnv.get(ConfigFactory.EASEAGENT_ENV_CONFIG)).thenReturn("{\"name\":\"env-service\"}"); Configs config = ConfigFactory.loadConfigs(null, this.getClass().getClassLoader()); assertEquals("env-service", config.getString(AGENT_SERVICE)); assertEquals("demo-system", config.getString(AGENT_SYSTEM)); } } @Test public void test_loadConfigs() { try (MockedStatic mockSystemEnv = Mockito.mockStatic(SystemEnv.class)) { mockSystemEnv.when(() -> SystemEnv.get("EASEAGENT_NAME")).thenReturn("service1"); mockSystemEnv.when(() -> SystemEnv.get("EASEAGENT_SYSTEM")).thenReturn("system1"); Configs config = ConfigFactory.loadConfigs(null, this.getClass().getClassLoader()); assertEquals("service1", config.getString(AGENT_SERVICE)); assertEquals("system1", config.getString(AGENT_SYSTEM)); System.setProperty("easeagent.name", "service2"); System.setProperty("easeagent.system", "system2"); config = ConfigFactory.loadConfigs(null, this.getClass().getClassLoader()); assertEquals("service2", config.getString(AGENT_SERVICE)); assertEquals("system2", config.getString(AGENT_SYSTEM)); } } @Test public void test_loadConfigsFromUserSpec() throws URISyntaxException { String userSpec = new File(this.getClass().getClassLoader().getResource("user-spec.properties").toURI()).getPath(); try (MockedStatic mockSystemEnv = Mockito.mockStatic(SystemEnv.class)) { mockSystemEnv.when(() -> SystemEnv.get("EASEAGENT_CONFIG_PATH")).thenReturn(userSpec); String path = ConfigFactory.getConfigPath(); assertEquals(userSpec, path); } } @Test public void test_loadConfigsFromOtelUserSpec() throws URISyntaxException { String userSpec = new File(this.getClass().getClassLoader().getResource("user-spec.properties").toURI()).getPath(); try (MockedStatic mockSystemEnv = Mockito.mockStatic(SystemEnv.class)) { mockSystemEnv.when(() -> SystemEnv.get("OTEL_JAVAAGENT_CONFIGURATION_FILE")).thenReturn(userSpec); String path = ConfigFactory.getConfigPath(); assertEquals(userSpec, path); } } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/ConfigPropertiesUtilsTest.java ================================================ /* * Copyright (c) 2022, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import org.junit.*; import org.junit.contrib.java.lang.system.EnvironmentVariables; import org.junit.contrib.java.lang.system.RestoreSystemProperties; public class ConfigPropertiesUtilsTest { @Rule public final EnvironmentVariables environmentVariables = new EnvironmentVariables(); @Rule public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); @BeforeClass public static void beforeClass() { // EnvironmentVariables and restoreSystemProperties does not work with Java 16 Assume.assumeTrue( System.getProperty("java.version").startsWith("1.8") || System.getProperty("java.version").startsWith("11") ); } @Test public void getString_systemProperty() { environmentVariables.set("TEST_PROPERTY_STRING", "env"); System.setProperty("test.property.string", "sys"); Assert.assertEquals("sys", ConfigPropertiesUtils.getString("test.property.string")); } @Test public void getString_environmentVariable() { environmentVariables.set("TEST_PROPERTY_STRING", "env"); Assert.assertEquals("env", ConfigPropertiesUtils.getString("test.property.string")); } @Test public void getString_none() { Assert.assertNull(ConfigPropertiesUtils.getString("test.property.string.none")); } @Test public void getInt_systemProperty() { environmentVariables.set("TEST_PROPERTY_INT", "12"); System.setProperty("test.property.int", "42"); Assert.assertEquals(42, ConfigPropertiesUtils.getInt("test.property.int", -1)); } @Test public void getInt_environmentVariable() { environmentVariables.set("TEST_PROPERTY_INT", "12"); Assert.assertEquals(12, ConfigPropertiesUtils.getInt("test.property.int", -1)); } @Test public void getInt_none() { Assert.assertEquals(-1, ConfigPropertiesUtils.getInt("test.property.int", -1)); } @Test public void getInt_invalidNumber() { System.setProperty("test.property.int", "not a number"); Assert.assertEquals(-1, ConfigPropertiesUtils.getInt("test.property.int", -1)); } @Test public void getBoolean_systemProperty() { environmentVariables.set("TEST_PROPERTY_BOOLEAN", "false"); System.setProperty("test.property.boolean", "true"); Assert.assertTrue(ConfigPropertiesUtils.getBoolean("test.property.boolean", false)); } @Test public void getBoolean_environmentVariable() { environmentVariables.set("TEST_PROPERTY_BOOLEAN", "true"); Assert.assertTrue(ConfigPropertiesUtils.getBoolean("test.property.boolean", false)); } @Test public void getBoolean_none() { Assert.assertFalse(ConfigPropertiesUtils.getBoolean("test.property.boolean", false)); } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/ConfigUtilsTest.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.api.config.Config; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.util.Collections; import java.util.Map; import static org.junit.Assert.assertTrue; public class ConfigUtilsTest { @Test public void test_bindProp() throws Exception { Configs configs = new Configs(Collections.singletonMap("hello", "world")); String[] rst = new String[]{null}; ConfigUtils.bindProp("hello", configs, Config::getString, v -> rst[0] = v); Assert.assertEquals("world", rst[0]); configs.updateConfigs(Collections.singletonMap("hello", "test")); Assert.assertEquals("test", rst[0]); configs.updateConfigs(Collections.singletonMap("hello", "one")); Assert.assertEquals("one", rst[0]); } @Test public void test_json2KVMap() throws Exception { Map map = ConfigUtils.json2KVMap("{\n" + " \"output\": {\n" + " \"servers\": \"127.0.0.1\",\n" + " \"timeout\": 1000,\n" + " \"enabled\": true,\n" + " \"arr\": [\"x\", { \"test\": 0 }]\n" + " },\n" + " \"hello\":null,\n" + " \"metrics\": {\n" + " \"obj\": {\n" + " \"a\": 1,\n" + " \"b\": \"2\",\n" + " \"c\": false\n" + " },\n" + " \"request\": {\n" + " \"topic\": \"hello\",\n" + " \"enabled\": false\n" + " }\n" + " }\n" + "}"); Assert.assertEquals("127.0.0.1", map.get("output.servers")); Assert.assertEquals("1000", map.get("output.timeout")); Assert.assertEquals("true", map.get("output.enabled")); Assert.assertEquals("x", map.get("output.arr.0")); Assert.assertEquals("0", map.get("output.arr.1.test")); Assert.assertEquals("", map.get("hello")); Assert.assertEquals("1", map.get("metrics.obj.a")); Assert.assertEquals("2", map.get("metrics.obj.b")); Assert.assertEquals("false", map.get("metrics.obj.c")); Assert.assertEquals("hello", map.get("metrics.request.topic")); Assert.assertEquals("false", map.get("metrics.request.enabled")); } @Test public void test_json2KVMap_2() throws IOException { Map map = ConfigUtils.json2KVMap("{\"serviceHeaders\":{\"mesh-app-backend\":[\"X-canary\"]}}"); Assert.assertEquals("X-canary", map.get("serviceHeaders.mesh-app-backend.0")); } @Test public void isGlobal() { Assert.assertTrue(ConfigUtils.isGlobal("global")); Assert.assertFalse(ConfigUtils.isGlobal("globaldf")); } @Test public void isPluginConfig() { Assert.assertTrue(ConfigUtils.isPluginConfig("plugin.")); Assert.assertFalse(ConfigUtils.isPluginConfig("plugin")); Assert.assertFalse(ConfigUtils.isPluginConfig("plugins.")); Assert.assertTrue(ConfigUtils.isPluginConfig("plugin.observability.kafka.kafka-trace", "observability", "kafka", "kafka-trace")); Assert.assertFalse(ConfigUtils.isPluginConfig("plugin.observability.kafka.kafka-trace", "observabilitys", "kafka", "kafka-trace")); Assert.assertFalse(ConfigUtils.isPluginConfig("plugin.observability.kafka.kafka-trace", "observability", "kafkas", "kafka-trace")); Assert.assertFalse(ConfigUtils.isPluginConfig("plugin.observability.kafka.kafka-trace", "observability", "kafka", "kafka-traces")); } @Test public void pluginProperty() { PluginProperty pluginProperty = ConfigUtils.pluginProperty("plugin.observability.kafka.self.enabled"); Assert.assertEquals("observability", pluginProperty.getDomain()); Assert.assertEquals("kafka", pluginProperty.getNamespace()); Assert.assertEquals("self", pluginProperty.getId()); Assert.assertEquals("enabled", pluginProperty.getProperty()); pluginProperty = ConfigUtils.pluginProperty("plugin.observability.kafka.self.tcp.enabled"); Assert.assertEquals("observability", pluginProperty.getDomain()); Assert.assertEquals("kafka", pluginProperty.getNamespace()); Assert.assertEquals("self", pluginProperty.getId()); Assert.assertEquals("tcp.enabled", pluginProperty.getProperty()); try { ConfigUtils.pluginProperty("plugin.observability.kafka.self"); assertTrue("must be error", false); } catch (Exception e) { Assert.assertNotNull(e); } } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/ConfigsTest.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.megaease.easeagent.plugin.api.config.ChangeItem; import com.megaease.easeagent.plugin.api.config.Config; import org.junit.Assert; import org.junit.Test; import java.util.Collections; import java.util.LinkedList; import java.util.List; public class ConfigsTest { @Test public void test_check_change_count1() throws Exception { final ObjectMapper json = new ObjectMapper(); GlobalConfigs configs = new GlobalConfigs(Collections.singletonMap("hello", "world")); List rst = addListener(configs); configs.updateService("{}", null); Assert.assertEquals(0, rst.size()); configs.updateService(json.writeValueAsString(Collections.singletonMap("hello", "world")), null); Assert.assertEquals(0, rst.size()); configs.updateService(json.writeValueAsString(Collections.singletonMap("hello", "world2")), null); Assert.assertEquals(1, rst.size()); } @Test public void test_check_change_count() { Configs configs = new Configs(Collections.singletonMap("hello", "world")); List rst = addListener(configs); configs.updateConfigs(Collections.emptyMap()); Assert.assertEquals(0, rst.size()); configs.updateConfigs(Collections.singletonMap("hello", "world")); Assert.assertEquals(0, rst.size()); configs.updateConfigs(Collections.singletonMap("hello", "world2")); Assert.assertEquals(1, rst.size()); } @Test public void test_check_old() { Configs configs = new Configs(Collections.singletonMap("hello", "world")); List rst = addListener(configs); configs.updateConfigs(Collections.singletonMap("hello", "test")); ChangeItem first = rst.get(0); Assert.assertEquals("hello", first.getFullName()); Assert.assertEquals("hello", first.getName()); Assert.assertEquals("world", first.getOldValue()); Assert.assertEquals("test", first.getNewValue()); } @Test public void test_check_new() { Configs configs = new Configs(Collections.singletonMap("hello", "world")); List rst = addListener(configs); configs.updateConfigs(Collections.singletonMap("name", "666")); ChangeItem first = rst.get(0); Assert.assertEquals("name", first.getFullName()); Assert.assertEquals("name", first.getName()); Assert.assertNull(first.getOldValue()); Assert.assertEquals("666", first.getNewValue()); } private List addListener(Config config) { List rst = new LinkedList<>(); config.addChangeListener(rst::addAll); return rst; } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/IPluginConfigConstTest.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.api.config.ConfigConst; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class IPluginConfigConstTest { @Test public void testExtractHeaderName() { String prefix = "globalCanaryHeaders.serviceHeaders"; assertEquals(ConfigConst.GlobalCanaryLabels.SERVICE_HEADERS, prefix); assertEquals("hello", ConfigConst.GlobalCanaryLabels.extractHeaderName(prefix + ".test-aaa.0.hello")); assertEquals("world", ConfigConst.GlobalCanaryLabels.extractHeaderName(prefix + ".test-aaa.1.world")); assertNull(ConfigConst.GlobalCanaryLabels.extractHeaderName(prefix + ".test-bbb")); assertNull(ConfigConst.GlobalCanaryLabels.extractHeaderName(prefix + "test-bbb")); } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/JarFileConfigLoaderTest.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.api.config.ConfigConst; import org.junit.After; import org.junit.Test; import java.io.File; import java.net.URISyntaxException; import static org.junit.Assert.*; public class JarFileConfigLoaderTest { @After public void after() { System.clearProperty(ConfigConst.AGENT_JAR_PATH); } @Test public void load() throws URISyntaxException { String fileName = "agent.properties"; assertNull(JarFileConfigLoader.load(null)); assertNull(JarFileConfigLoader.load(fileName)); String jarPath = new File(this.getClass().getClassLoader().getResource("easeagent_config.jar").toURI()).getPath(); System.setProperty(ConfigConst.AGENT_JAR_PATH, jarPath); GlobalConfigs config = JarFileConfigLoader.load(fileName); assertNotNull(config); assertEquals("demo-jar-service", config.getString("name")); assertEquals("demo-system", config.getString("system")); config = JarFileConfigLoader.load("agent.yaml"); assertNotNull(config); assertEquals("test-jar-service", config.getString("name")); assertEquals("demo-system", config.getString("system")); config = JarFileConfigLoader.load("agentaaaa.yaml"); assertNull(config); String nullJarPath = new File("easeagent_config_null.jar").getPath(); System.setProperty(ConfigConst.AGENT_JAR_PATH, nullJarPath); config = JarFileConfigLoader.load("agent.yaml"); assertNull(config); } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/OtelSdkConfigsTest.java ================================================ /* * Copyright (c) 2022, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.utils.SystemEnv; import org.junit.After; import org.junit.Assert; import org.junit.Test; import java.lang.reflect.Field; import java.util.Map; public class OtelSdkConfigsTest { @After public void after() throws NoSuchFieldException, IllegalAccessException { Field field = SystemEnv.class.getDeclaredField("ENVIRONMENTS"); field.setAccessible(true); Object env = field.get(null); field.setAccessible(false); Map envMap = (Map) env; envMap.remove("OTEL_RESOURCE_ATTRIBUTES"); envMap.remove("OTEL_SERVICE_NAME"); envMap.remove("OTEL_SERVICE_NAMESPACE"); System.clearProperty("otel.service.name"); System.clearProperty("otel.service.namespace"); } @Test public void updateEnvCfg() { //value from system env "OTEL_RESOURCE_ATTRIBUTES String attributes = "service.name=service1,service.namespace=namespace1"; SystemEnv.set("OTEL_RESOURCE_ATTRIBUTES", attributes); Map envCfg = OtelSdkConfigs.updateEnvCfg(); Assert.assertEquals("service1", envCfg.get("name")); Assert.assertEquals("namespace1", envCfg.get("system")); // override by system env SystemEnv.set("OTEL_SERVICE_NAME", "service2"); SystemEnv.set("OTEL_SERVICE_NAMESPACE", "namespace2"); envCfg = OtelSdkConfigs.updateEnvCfg(); Assert.assertEquals("service2", envCfg.get("name")); Assert.assertEquals("namespace2", envCfg.get("system")); // override by system property System.setProperty("otel.service.name", "service3"); System.setProperty("otel.service.namespace", "namespace3"); envCfg = OtelSdkConfigs.updateEnvCfg(); Assert.assertEquals("service3", envCfg.get("name")); Assert.assertEquals("namespace3", envCfg.get("system")); } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/PluginConfigManagerTest.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.api.config.IPluginConfig; import com.megaease.easeagent.plugin.api.config.PluginConfigChangeListener; import org.junit.Test; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.*; public class PluginConfigManagerTest { public static String DOMAIN = "testDomain"; public static String NAMESPACE = "testNamespace"; public static String TEST_TRACE_ID = "test-trace"; public static String GLOBAL_ID = TEST_TRACE_ID; public static String TEST_METRIC_ID = "test-metric"; public static String TEST_AAA_ID = "test-AAA"; PluginConfigManager build() { return build(PluginSourceConfigTest.buildSource()); } PluginConfigManager build(Map source) { Configs configs = new Configs(source); return PluginConfigManager.builder(configs).build(); } @Test public void testBuild() { build(); assertTrue(true); } @Test public void getConfig() { PluginConfigManager pluginConfigManager = build(); for (Map.Entry entry : PluginSourceConfigTest.buildSource().entrySet()) { assertEquals(pluginConfigManager.getConfig(entry.getKey()), entry.getValue()); } } private void checkPluginConfigString(PluginConfig pluginConfig, Map source) { for (Map.Entry entry : source.entrySet()) { assertEquals(pluginConfig.getString(entry.getKey()), entry.getValue()); } } public static Map buildSource() { Map source = getSource("global", GLOBAL_ID, PluginConfigTest.globalSource()); source.putAll(getSource("global", TEST_AAA_ID, PluginConfigTest.globalSource())); source.putAll(getSource(NAMESPACE, TEST_TRACE_ID, PluginConfigTest.coverSource())); source.put("plugin.testDomain.testssss.self.lll", "aaa"); source.put("plugin.testDomain.testssss.kafka.lll", "aaa"); source.put("plugin.testDomain.testssss.kafka.lll", "aaa"); source.put("plugin.testDomain.testssss.kafka.lll", "aaa"); return source; } public static Map getSource(String namespace, String id, Map properties) { Map s = new HashMap<>(); for (Map.Entry pEntry : properties.entrySet()) { s.put("plugin." + DOMAIN + "." + namespace + "." + id + "." + pEntry.getKey(), pEntry.getValue()); } return s; } @Test public void getConfig1() throws InterruptedException { PluginConfigManager pluginConfigManager = build(); PluginConfig pluginConfig = pluginConfigManager.getConfig(PluginSourceConfigTest.DOMAIN, "global", PluginSourceConfigTest.GLOBAL_ID); checkPluginConfigString(pluginConfig, PluginConfigTest.globalSource()); pluginConfig = pluginConfigManager.getConfig(PluginSourceConfigTest.DOMAIN, PluginSourceConfigTest.NAMESPACE, PluginSourceConfigTest.TEST_TRACE_ID); checkPluginConfigString(pluginConfig, PluginConfigTest.globalSource()); pluginConfig = pluginConfigManager.getConfig(PluginSourceConfigTest.DOMAIN, PluginSourceConfigTest.NAMESPACE, PluginSourceConfigTest.TEST_METRIC_ID); checkPluginConfigString(pluginConfig, PluginConfigTest.globalSource()); Map source = buildSource(); source.putAll(getSource("trace", TEST_TRACE_ID, PluginConfigTest.coverSource())); source.putAll(getSource("trace", GLOBAL_ID, PluginConfigTest.coverSource())); build(source); Configs configs = new Configs(buildSource()); pluginConfigManager = PluginConfigManager.builder(configs).build(); final PluginConfig pluginConfig1 = pluginConfigManager.getConfig(DOMAIN, NAMESPACE, TEST_TRACE_ID); final AtomicReference oldPluginConfig = new AtomicReference<>(); final AtomicReference newPluginConfig = new AtomicReference<>(); PluginConfigTest.checkAllType(pluginConfig1); pluginConfig1.addChangeListener((oldConfig, newConfig) -> { oldPluginConfig.set(oldConfig); newPluginConfig.set(newConfig); }); configs.updateConfigs(Collections.singletonMap(String.format("plugin.%s.%s.%s.enabled", DOMAIN, NAMESPACE, TEST_TRACE_ID), "false")); assertNotNull(oldPluginConfig.get()); assertNotNull(newPluginConfig.get()); assertTrue(oldPluginConfig.get() == pluginConfig1); final AtomicReference oldPluginConfigListener = new AtomicReference<>(); final AtomicReference newPluginConfigListener = new AtomicReference<>(); ((PluginConfig) oldPluginConfig.get()).foreachConfigChangeListener(listener -> oldPluginConfigListener.set(listener)); ((PluginConfig) newPluginConfig.get()).foreachConfigChangeListener(listener -> newPluginConfigListener.set(listener)); assertTrue(oldPluginConfigListener.get() == newPluginConfigListener.get()); assertTrue(oldPluginConfig.get().getBoolean("enabled")); assertFalse(newPluginConfig.get().getBoolean("enabled")); PluginConfigTest.checkAllType((PluginConfig) oldPluginConfig.get()); configs.updateConfigs(Collections.singletonMap(String.format("plugin.%s.%s.%s.enabled", DOMAIN, NAMESPACE, TEST_TRACE_ID), "true")); assertFalse(oldPluginConfig.get().getBoolean("enabled")); assertTrue(newPluginConfig.get().getBoolean("enabled")); configs.updateConfigs(Collections.singletonMap(String.format("plugin.%s.global.%s.enabled", DOMAIN, TEST_TRACE_ID), "false")); assertTrue(oldPluginConfig.get().getBoolean("enabled")); assertFalse(newPluginConfig.get().getBoolean("enabled")); IPluginConfig newConfig = newPluginConfig.get(); configs.updateConfigs(Collections.singletonMap(String.format("plugin.%s.global.%s.enabled", DOMAIN, TEST_AAA_ID), "false")); assertTrue(newPluginConfig.get() == newConfig); configs.updateConfigs(Collections.singletonMap(String.format("ssss.%s.global.%s.enabled", DOMAIN, TEST_AAA_ID), "false")); } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/PluginConfigTest.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import com.megaease.easeagent.plugin.api.config.PluginConfigChangeListener; import org.junit.Test; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import static org.junit.Assert.*; public class PluginConfigTest { public static Map globalSource() { Map global = new HashMap<>(); global.put("enabled", "true"); global.put("tcp.enabled", "true"); global.put("host", "127.0.0.1"); global.put("count", "127"); global.put("double", "127.1"); global.put("double_1", "127.2"); global.put("list", "a,b,c"); return global; } public static Map coverSource() { Map cover = new HashMap<>(); cover.put("tcp.enabled", "false"); cover.put("http.enabled", "true"); cover.put("host", "127.0.0.3"); cover.put("count", "127"); cover.put("double", "127.3"); cover.put("list", "a,b,c"); return cover; } PluginConfig build() { String domain = "testdomain"; String id = "testid"; String namespace = "NAMESPACE"; Map global = globalSource(); Map cover = coverSource(); return PluginConfig.build(domain, id, global, namespace, cover, null); } @Test public void domain() { assertEquals("testdomain", build().domain()); } @Test public void namespace() { assertEquals("NAMESPACE", build().namespace()); } @Test public void id() { assertEquals("testid", build().id()); } @Test public void hasProperty() { checkHasProperty(build()); } public static void checkHasProperty(PluginConfig config) { assertTrue(config.hasProperty("enabled")); assertTrue(config.hasProperty("tcp.enabled")); assertTrue(config.hasProperty("http.enabled")); assertFalse(config.hasProperty("http.enabled.cccc")); } @Test public void getString() { checkString(build()); } public static void checkString(PluginConfig config) { assertEquals("true", config.getString("enabled")); assertEquals("false", config.getString("tcp.enabled")); assertEquals("127", config.getString("count")); assertEquals("127.0.0.3", config.getString("host")); assertEquals("true", config.getString("http.enabled")); assertNull(config.getString("http.enabled.sss")); } @Test public void getInt() { checkInt(build()); } public static void checkInt(PluginConfig config) { assertEquals(127, (int) config.getInt("count")); assertNull(config.getInt("enabled")); assertNull(config.getInt("cccccccccccccc")); } @Test public void getBoolean() { checkBoolean(build()); } public static void checkBoolean(PluginConfig config) { assertTrue(config.getBoolean("enabled")); assertFalse(config.getBoolean("tcp.enabled")); assertFalse(config.getBoolean("http.enabled")); assertFalse(config.getBoolean("http.enabled.ssss")); } @Test public void getDouble() { checkDouble(build()); } public static void checkDouble(PluginConfig config) { assertTrue(Math.abs(config.getDouble("double") - 127.3) < 0.0001); assertTrue(Math.abs(config.getDouble("double_1") - 127.2) < 0.0001); assertNull(config.getDouble("enabled")); } @Test public void getLong() { checkLong(build()); } public static void checkLong(PluginConfig config) { assertEquals(127l, (long) config.getLong("count")); assertNull(config.getLong("enabled")); assertNull(config.getLong("cccccccccccccc")); } @Test public void getStringList() { checkStringList(build()); } public static void checkStringList(PluginConfig config) { List list = config.getStringList("list"); assertEquals(3, list.size()); assertEquals("a", list.get(0)); assertEquals("b", list.get(1)); assertEquals("c", list.get(2)); } @Test public void addChangeListener() { PluginConfig config = build(); config.addChangeListener((oldConfig, newConfig) -> { }); AtomicInteger count = new AtomicInteger(0); config.foreachConfigChangeListener(new Consumer() { @Override public void accept(PluginConfigChangeListener listener) { count.incrementAndGet(); } }); assertEquals(1, count.get()); } @Test public void getConfigChangeListener() { addChangeListener(); } @Test public void keySet() { checkKeySet(build()); } public static void checkKeySet(PluginConfig config) { Set set = config.keySet(); Map source = globalSource(); source.putAll(coverSource()); assertEquals(set.size(), source.size()); for (String s : set) { assertTrue(source.containsKey(s)); } } public static void checkAllType(PluginConfig config) { checkHasProperty(config); checkString(config); checkBoolean(config); checkInt(config); checkLong(config); checkDouble(config); checkStringList(config); checkKeySet(config); } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/PluginPropertyTest.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import org.junit.Test; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.*; public class PluginPropertyTest { public PluginProperty build() { return new PluginProperty("testdomain", "testnamespace", "testId", "testproperty"); } @Test public void getDomain() { assertEquals("testdomain", build().getDomain()); } @Test public void getNamespace() { assertEquals("testnamespace", build().getNamespace()); } @Test public void getId() { assertEquals("testId", build().getId()); } @Test public void getProperty() { assertEquals("testproperty", build().getProperty()); } @Test public void testEquals() { assertTrue(build().equals(build())); PluginProperty property = new PluginProperty("testdomain", "testnamespace", "testId", "testproperty1"); assertFalse(build().equals(property)); Map pluginPropertyStringMap = new HashMap<>(); pluginPropertyStringMap.put(build(), "testValue"); assertTrue(pluginPropertyStringMap.containsKey(build())); assertEquals("testValue", pluginPropertyStringMap.get(build())); assertFalse(pluginPropertyStringMap.containsKey(property)); } @Test public void testHashCode() { assertEquals(build().hashCode(), build().hashCode()); PluginProperty property = new PluginProperty("testdomain", "testnamespace", "testId", "testproperty1"); assertNotEquals(build().hashCode(), property.hashCode()); } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/PluginSourceConfigTest.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import org.junit.Test; import java.util.Collections; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; public class PluginSourceConfigTest { public static String DOMAIN = "testDomain"; public static String NAMESPACE = "testNamespace"; public static String TEST_TRACE_ID = "test-trace"; public static String GLOBAL_ID = TEST_TRACE_ID; public static String TEST_METRIC_ID = "test-metric"; public static String TEST_AAA_ID = "test-AAA"; public static Map getSource(String namespace, String id) { Map properties = PluginConfigTest.globalSource(); Map s = new HashMap<>(); for (Map.Entry pEntry : properties.entrySet()) { s.put("plugin." + DOMAIN + "." + namespace + "." + id + "." + pEntry.getKey(), pEntry.getValue()); } return s; } public static Map getSource(String id) { return getSource(NAMESPACE, id); } public static Map getGlobal() { return getSource("global", GLOBAL_ID); } public static Map buildSource() { Map source = getGlobal(); source.putAll(getSource(TEST_METRIC_ID)); source.putAll(getSource(TEST_TRACE_ID)); source.put("plugin.testDomain.testssss.self.lll", "aaa"); source.put("plugin.testDomain.testssss.kafka.lll", "aaa"); source.put("plugin.testDomain.testssss.kafka.lll", "aaa"); source.put("plugin.testDomain.testssss.kafka.lll", "aaa"); return source; } PluginSourceConfig buildImpl(String namespace, String id) { Map source = buildSource(); return PluginSourceConfig.build(DOMAIN, namespace, id, source); } @Test public void build() { PluginSourceConfig config = buildImpl(NAMESPACE, TEST_TRACE_ID); assertNotNull(config); } @Test public void getSource() { assertEquals(buildImpl("global", GLOBAL_ID).getSource(), getGlobal()); assertEquals(Collections.emptyMap(), buildImpl(NAMESPACE, TEST_AAA_ID).getSource()); assertEquals(getSource(TEST_TRACE_ID), buildImpl(NAMESPACE, TEST_TRACE_ID).getSource()); assertEquals(getSource(TEST_METRIC_ID), buildImpl(NAMESPACE, TEST_METRIC_ID).getSource()); } @Test public void getDomain() { assertEquals(buildImpl(NAMESPACE, TEST_AAA_ID).getDomain(), DOMAIN); assertEquals(buildImpl(NAMESPACE, TEST_TRACE_ID).getDomain(), DOMAIN); assertEquals(buildImpl(NAMESPACE, TEST_METRIC_ID).getDomain(), DOMAIN); } @Test public void getNamespace() { assertEquals(buildImpl(NAMESPACE, TEST_AAA_ID).getNamespace(), NAMESPACE); assertEquals(buildImpl(NAMESPACE, TEST_TRACE_ID).getNamespace(), NAMESPACE); assertEquals(buildImpl(NAMESPACE, TEST_METRIC_ID).getNamespace(), NAMESPACE); } @Test public void getId() { assertEquals(buildImpl(NAMESPACE, TEST_AAA_ID).getId(), TEST_AAA_ID); assertEquals(buildImpl(NAMESPACE, TEST_TRACE_ID).getId(), TEST_TRACE_ID); assertEquals(buildImpl(NAMESPACE, TEST_METRIC_ID).getId(), TEST_METRIC_ID); } @Test public void getProperties() { assertEquals(buildImpl("global", GLOBAL_ID).getProperties(), PluginConfigTest.globalSource()); assertEquals(buildImpl(NAMESPACE, TEST_TRACE_ID).getProperties(), PluginConfigTest.globalSource()); assertEquals(buildImpl(NAMESPACE, TEST_METRIC_ID).getProperties(), PluginConfigTest.globalSource()); assertEquals(buildImpl(NAMESPACE, TEST_AAA_ID).getProperties(), new HashMap<>()); } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/ValidateUtilsTest.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config; import org.junit.Assert; import org.junit.Test; import java.util.Collections; import static com.megaease.easeagent.config.ValidateUtils.*; public class ValidateUtilsTest { @Test public void test_hasText() throws Exception { Configs configs = new Configs(Collections.emptyMap()); try { ValidateUtils.validate(configs, "hello", HasText); Assert.fail("Never get here."); } catch (ValidateUtils.ValidException e) { Assert.assertTrue(e.getMessage().contains("has no non-empty value")); } } @Test public void test_numberInt() throws Exception { Configs configs = new Configs(Collections.singletonMap("hello", "test")); try { ValidateUtils.validate(configs, "hello", HasText, NumberInt); Assert.fail("Never get here."); } catch (ValidateUtils.ValidException e) { Assert.assertTrue(e.getMessage().contains("has no integer value")); } } @Test public void test_numberInt2() throws Exception { Configs configs = new Configs(Collections.singletonMap("hello", "100")); try { ValidateUtils.validate(configs, "hello", HasText, NumberInt); } catch (ValidateUtils.ValidException e) { Assert.fail("Never get here."); } } @Test public void test_bool() throws Exception { Configs configs = new Configs(Collections.singletonMap("hello", "test")); try { ValidateUtils.validate(configs, "hello", HasText, Bool); Assert.fail("Never get here."); } catch (ValidateUtils.ValidException e) { Assert.assertTrue(e.getMessage().contains("has no boolean value")); } } @Test public void test_bool2() throws Exception { Configs configs = new Configs(Collections.singletonMap("hello", "true")); try { ValidateUtils.validate(configs, "hello", HasText, Bool); } catch (ValidateUtils.ValidException e) { Assert.fail("Never get here."); } } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/report/ReportConfigAdapterTest.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.config.report; import com.megaease.easeagent.config.GlobalConfigs; import com.megaease.easeagent.plugin.utils.NoNull; import org.junit.Assert; import org.junit.Test; import java.util.HashMap; import static com.megaease.easeagent.config.report.ReportConfigConst.*; public class ReportConfigAdapterTest { @Test public void test_zipkin_target() { HashMap cfg = new HashMap<>(); String url = "http://localhost:9411/api/v2/spans"; cfg.put("observability.tracings.output.target", "zipkin"); cfg.put("observability.tracings.output.target.zipkinUrl", url); GlobalConfigs configs = new GlobalConfigs(cfg); String senderName = configs.getString(TRACE_SENDER_NAME); Assert.assertEquals(ZIPKIN_SENDER_NAME, senderName); Assert.assertEquals(url, configs.getString(join(TRACE_SENDER, "url"))); } @Test public void test_console_target() { HashMap cfg = new HashMap<>(); String url = ""; cfg.put("observability.tracings.output.target", "zipkin"); cfg.put("observability.tracings.output.target.zipkinUrl", url); GlobalConfigs configs = new GlobalConfigs(cfg); Assert.assertEquals(CONSOLE_SENDER_NAME, configs.getString(TRACE_SENDER_NAME)); cfg.clear(); cfg.put("observability.tracings.output.target", "system"); cfg.put("observability.tracings.output.topic", "log-tracing"); configs = new GlobalConfigs(cfg); Assert.assertEquals(CONSOLE_SENDER_NAME, configs.getString(TRACE_SENDER_NAME)); } @Test public void test_kafka_target() { HashMap cfg = new HashMap<>(); cfg.put("observability.tracings.output.target", "system"); cfg.put("observability.tracings.output.topic", "log-tracing"); cfg.put("observability.outputServer.bootstrapServer", "127.0.0.1:9092"); GlobalConfigs configs = new GlobalConfigs(cfg); Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(TRACE_SENDER_NAME)); } @Test public void test_trace_output() { /* * observability.tracings.output.messageMaxBytes=999900 * observability.tracings.output.reportThread=1 * observability.tracings.output.queuedMaxSpans=1000 * observability.tracings.output.queuedMaxSize=1000000 * observability.tracings.output.messageTimeout=1000 */ HashMap cfg = new HashMap<>(); cfg.put("observability.tracings.output.messageMaxBytes", "123"); cfg.put("observability.tracings.output.reportThread", "2"); GlobalConfigs configs = new GlobalConfigs(cfg); long m = configs.getLong(TRACE_ASYNC_MESSAGE_MAX_BYTES_V2); Assert.assertEquals(123L, m); m = configs.getLong(TRACE_ASYNC_REPORT_THREAD_V2); Assert.assertEquals(2L, m); // override by v2 cfg.clear(); cfg.put("observability.tracings.output.queuedMaxSpans", "123"); cfg.put(TRACE_ASYNC_QUEUED_MAX_SPANS_V2, "1000"); configs = new GlobalConfigs(cfg); m = configs.getLong(TRACE_ASYNC_QUEUED_MAX_SPANS_V2); Assert.assertEquals(1000L, m); } @Test public void test_metric_global_v1() { /* * plugin.observability.global.metric.interval=30 * plugin.observability.global.metric.topic=application-meter * plugin.observability.global.metric.appendType=kafka */ HashMap cfg = new HashMap<>(); cfg.put("plugin.observability.global.metric.interval", "30"); cfg.put("plugin.observability.global.metric.topic", "application-meter"); cfg.put("plugin.observability.global.metric.appendType", "kafka"); GlobalConfigs configs = new GlobalConfigs(cfg); Assert.assertEquals(30L, (long)NoNull.of(configs.getLong(METRIC_ASYNC_INTERVAL), 0L)); Assert.assertEquals("application-meter", configs.getString(METRIC_SENDER_TOPIC)); Assert.assertEquals(METRIC_KAFKA_SENDER_NAME, configs.getString(METRIC_SENDER_NAME)); } @Test public void test_metric_async_update() { HashMap cfg = new HashMap<>(); cfg.put("plugin.observability.global.metric.output.messageMaxBytes", "100"); cfg.put("plugin.observability.access.metric.output.interval", "100"); cfg.put(METRIC_ASYNC_MESSAGE_MAX_BYTES, "1000"); cfg.put(METRIC_ASYNC_INTERVAL, "200"); GlobalConfigs configs = new GlobalConfigs(cfg); Assert.assertEquals("1000", configs.getString(METRIC_ASYNC_MESSAGE_MAX_BYTES)); Assert.assertEquals("1000", configs.getString(join(METRIC_V2, "access", ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY))); Assert.assertEquals("100", configs.getString(join(METRIC_V2, "access", ASYNC_KEY, INTERVAL_KEY))); HashMap changes = new HashMap<>(); changes.put(METRIC_ASYNC_MESSAGE_MAX_BYTES, "2000"); changes.put(METRIC_ASYNC_INTERVAL, "150"); configs.updateConfigs(changes); Assert.assertEquals("2000", configs.getString(join(METRIC_V2, "access", ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY))); Assert.assertEquals("2000", configs.getString(METRIC_ASYNC_MESSAGE_MAX_BYTES)); Assert.assertEquals("100", configs.getString(join(METRIC_V2, "access", ASYNC_KEY, INTERVAL_KEY))); } @Test public void test_log_global() { HashMap cfg = new HashMap<>(); cfg.put("plugin.observability.global.log.topic", "application-log"); cfg.put("plugin.observability.global.log.url", "/application-log"); cfg.put("plugin.observability.global.log.appendType", "kafka"); cfg.put("plugin.observability.global.log.output.messageMaxBytes", "100"); GlobalConfigs configs = new GlobalConfigs(cfg); Assert.assertEquals("application-log", configs.getString(LOG_SENDER_TOPIC)); Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(LOG_SENDER_NAME)); Assert.assertEquals("100", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES)); } @Test public void test_access_in_metric() { HashMap cfg = new HashMap<>(); cfg.put("plugin.observability.global.log.topic", "application-log"); cfg.put("plugin.observability.global.log.url", "/application-log"); cfg.put("plugin.observability.global.log.appendType", "kafka"); cfg.put("plugin.observability.global.log.encoder", "APP_JSON"); cfg.put("plugin.observability.global.log.output.messageMaxBytes", "100"); cfg.put("plugin.observability.access.log.topic", "access-log"); cfg.put("plugin.observability.access.log.encoder", "LOG_ACCESS_JSON"); cfg.put("plugin.observability.access.metric.encoder", "ACCESS_JSON"); GlobalConfigs configs = new GlobalConfigs(cfg); Assert.assertEquals("application-log", configs.getString(LOG_SENDER_TOPIC)); Assert.assertEquals("access-log", configs.getString(LOG_ACCESS_SENDER_TOPIC)); Assert.assertEquals("ACCESS_JSON", configs.getString(LOG_ACCESS_ENCODER)); Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(LOG_ACCESS_SENDER_NAME)); Assert.assertEquals("100", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES)); } @Test public void test_access_log_update() { HashMap cfg = new HashMap<>(); cfg.put("plugin.observability.global.log.topic", "application-log"); cfg.put("plugin.observability.global.log.url", "/application-log"); cfg.put("plugin.observability.global.log.appendType", "kafka"); cfg.put("plugin.observability.global.log.encoder", "APP_JSON"); cfg.put("plugin.observability.global.log.output.messageMaxBytes", "100"); cfg.put("plugin.observability.access.log.topic", "access-log"); cfg.put("plugin.observability.access.log.encoder", "LOG_ACCESS_JSON"); GlobalConfigs configs = new GlobalConfigs(cfg); Assert.assertEquals("application-log", configs.getString(LOG_SENDER_TOPIC)); Assert.assertEquals("access-log", configs.getString(LOG_ACCESS_SENDER_TOPIC)); Assert.assertEquals("LOG_ACCESS_JSON", configs.getString(LOG_ACCESS_ENCODER)); Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(LOG_ACCESS_SENDER_NAME)); Assert.assertEquals("100", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES)); // update HashMap changes = new HashMap<>(); changes.put("plugin.observability.access.log.appendType", CONSOLE_SENDER_NAME); configs.updateConfigs(changes); Assert.assertEquals(CONSOLE_SENDER_NAME, configs.getString(LOG_ACCESS_SENDER_NAME)); changes.put("plugin.observability.access.metric.appendType", KAFKA_SENDER_NAME); changes.put("plugin.observability.access.log.appendType", CONSOLE_SENDER_NAME); configs.updateConfigs(changes); Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(LOG_ACCESS_SENDER_NAME)); } @Test public void test_log_global_async() { HashMap cfg = new HashMap<>(); cfg.put("plugin.observability.global.log.topic", "application-log"); cfg.put("plugin.observability.global.log.url", "/application-log"); cfg.put("plugin.observability.global.log.appendType", "kafka"); cfg.put("plugin.observability.global.log.output.messageMaxBytes", "100"); cfg.put("plugin.observability.access.log.topic", "access-log"); cfg.put("plugin.observability.access.log.encoder", "LOG_ACCESS_JSON"); cfg.put(LOG_ASYNC_MESSAGE_MAX_BYTES, "1000"); cfg.put(LOG_ASYNC_QUEUED_MAX_LOGS, "200"); GlobalConfigs configs = new GlobalConfigs(cfg); Assert.assertEquals("application-log", configs.getString(LOG_SENDER_TOPIC)); Assert.assertEquals("access-log", configs.getString(LOG_ACCESS_SENDER_TOPIC)); Assert.assertEquals("LOG_ACCESS_JSON", configs.getString(LOG_ACCESS_ENCODER)); Assert.assertEquals(KAFKA_SENDER_NAME, configs.getString(LOG_ACCESS_SENDER_NAME)); Assert.assertEquals("1000", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES)); Assert.assertEquals("1000", configs.getString(join(LOG_ACCESS, ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY))); } @Test public void test_log_async_update() { HashMap cfg = new HashMap<>(); cfg.put("plugin.observability.global.log.output.messageMaxBytes", "100"); cfg.put("plugin.observability.access.log.output.queuedMaxLogs", "100"); cfg.put(LOG_ASYNC_MESSAGE_MAX_BYTES, "1000"); cfg.put(LOG_ASYNC_QUEUED_MAX_LOGS, "200"); GlobalConfigs configs = new GlobalConfigs(cfg); Assert.assertEquals("1000", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES)); Assert.assertEquals("1000", configs.getString(join(LOG_ACCESS, ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY))); Assert.assertEquals("100", configs.getString(join(LOG_ACCESS, ASYNC_KEY, ASYNC_QUEUE_MAX_LOGS_KEY))); HashMap changes = new HashMap<>(); changes.put(LOG_ASYNC_MESSAGE_MAX_BYTES, "2000"); changes.put(LOG_ASYNC_QUEUED_MAX_LOGS, "150"); configs.updateConfigs(changes); Assert.assertEquals("2000", configs.getString(join(LOG_ACCESS, ASYNC_KEY, ASYNC_MSG_MAX_BYTES_KEY))); Assert.assertEquals("2000", configs.getString(LOG_ASYNC_MESSAGE_MAX_BYTES)); Assert.assertEquals("100", configs.getString(join(LOG_ACCESS, ASYNC_KEY, ASYNC_QUEUE_MAX_LOGS_KEY))); } } ================================================ FILE: config/src/test/java/com/megaease/easeagent/config/yaml/YamlReaderTest.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.config.yaml; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; public class YamlReaderTest { private static final String CONFIG_FILE_YAML = "agent.yaml"; private static final String CONFIG_FILE_PROPERTIES = "agent.properties"; @Test public void testCompress() { InputStream inputYaml = Thread.currentThread().getContextClassLoader().getResourceAsStream(CONFIG_FILE_YAML); Map yamlMap = new YamlReader().load(inputYaml).compress(); InputStream inputProperties = YamlReaderTest.class.getClassLoader().getResourceAsStream(CONFIG_FILE_PROPERTIES); Map yamlProperties = extractPropsMap(inputProperties); yamlMap.forEach((k, v) -> { Assert.assertTrue(yamlProperties.containsKey(k)); if (!k.equals("name")) { Assert.assertEquals(v, yamlProperties.get(k)); } }); yamlProperties.forEach((k, v) -> { Assert.assertTrue(yamlMap.containsKey(k)); if (!k.equals("name")) { Assert.assertEquals(v, yamlMap.get(k)); } }); } private static Map extractPropsMap(InputStream in) { Properties properties = new Properties(); try { properties.load(in); HashMap map = new HashMap<>(); for (String one : properties.stringPropertyNames()) { map.put(one, properties.getProperty(one)); } return map; } catch (IOException e) { e.printStackTrace(); } return Collections.emptyMap(); } } ================================================ FILE: config/src/test/resources/agent.properties ================================================ name=demo-service system=demo-system user.list=a,b ### http server # When the enabled value = false, agent will not start the http server # You can use -Deaseagent.server.enabled=[true | false] to override. easeagent.server.enabled=true # http server port. You can use -Deaseagent.server.port=[port] to override. easeagent.server.port=9900 # Enable health/readiness easeagent.health.readiness.enabled=true # forwarded headers page # Pass-through headers from the root process all the way to the end # format: easeagent.progress.forwarded.headers={headerName} #easeagent.progress.forwarded.headers=X-Forwarded-For #easeagent.progress.forwarded.headers=X-Location,X-Mesh-Service-Canary,X-Phone-Os ### ### default tracings reporter configuration ### # sampledType: ## counting: percentage sampling, sampled limit 0.01 to 1, 1 is always sample, 0 is never sample, 0.1 is ten samples per hundred ## rate_limiting: traces per second, sampled >= 0, 0 is never sample, 10 is max 10 traces per second ## boundary: percentage sampling by traceId, sampled limit 0.0001 to 1, 1 is always sample, 0 is never sample ## if sampled=0.001, when (traceId^random)%10000<=(0.001*10000) sampled ## sampledType must be used with sampled, otherwise the default value is used Sampler.ALWAYS_SAMPLE observability.tracings.sampledType= observability.tracings.sampled=1 # get header from response headers then tag to tracing span # format: observability.tracings.tag.response.headers.{key}={value} # support ease mesh # X-EG-Circuit-Breaker # X-EG-Retryer # X-EG-Rate-Limiter # X-EG-Time-Limiter observability.tracings.tag.response.headers.eg.0=X-EG-Circuit-Breaker observability.tracings.tag.response.headers.eg.1=X-EG-Retryer observability.tracings.tag.response.headers.eg.2=X-EG-Rate-Limiter observability.tracings.tag.response.headers.eg.3=X-EG-Time-Limiter # -------------------- plugin global config --------------------- plugin.observability.global.tracing.enabled=true plugin.observability.global.metric.enabled=true plugin.observability.global.metric.interval=30 plugin.observability.global.metric.topic=application-meter # plugin.observability.global.metric.appendType=console ## output by http #plugin.observability.global.metric.appendType=http # add service name to header enabled by name for easemesh plugin.integrability.global.addServiceNameHead.enabled=true # redirect the middleware address when env has address, see: com.megaease.easeagent.plugin.api.middleware.RedirectProcessor # about redirect: jdbc, kafka, rabbitmq, redis, plugin.integrability.global.redirect.enabled=true # forwarded headers enabled. # headers see config: easeagent.progress.forwarded.headers.???=??? plugin.integrability.global.forwarded.enabled=true plugin.hook.global.foundation.enabled=true # ---------------------------------------------- # if the plugin configuration is consistent with the global namespace, # do not add configuration items not commented out in this default configuration file. # otherwise, they can not be overridden by Global configuration in user's configuration file. # # -------------------- jvm --------------------- # plugin.observability.jvmGc.metric.enabled=true # plugin.observability.jvmGc.metric.interval=30 plugin.observability.jvmGc.metric.topic=platform-metrics plugin.observability.jvmGc.metric.url=/platform-metrics # plugin.observability.jvmGc.metric.appendType=kafka # plugin.observability.jvmMemory.metric.enabled=true # plugin.observability.jvmMemory.metric.interval=30 plugin.observability.jvmMemory.metric.topic=platform-metrics plugin.observability.jvmMemory.metric.url=/platform-metrics # plugin.observability.jvmMemory.metric.appendType=kafka # # -------------------- async --------------------- # plugin.observability.async.tracing.enabled=true # # -------------------- elasticsearch redirect --------------------- # plugin.integrability.elasticsearch.redirect.enabled=true # plugin.observability.elasticsearch.tracing.enabled=true # elasticsearch metric # plugin.observability.elasticsearch.metric.enabled=true # plugin.observability.elasticsearch.metric.interval=30 plugin.observability.elasticsearch.metric.topic=platform-metrics plugin.observability.elasticsearch.metric.url=/platform-metrics # plugin.observability.elasticsearch.metric.appendType=kafka # # -------------------- httpServlet --------------------- # plugin.observability.httpServlet.tracing.enabled=true # plugin.observability.httpServlet.metric.enabled=true # plugin.observability.httpServlet.metric.interval=30 plugin.observability.httpServlet.metric.topic=application-metrics plugin.observability.httpServlet.metric.url=/application-metrics # plugin.observability.httpServlet.metric.appendType=kafka # # -------------------- jdbc --------------------- ## jdbc tracing # plugin.observability.jdbc.tracing.enabled=true # jdbcStatement metric # plugin.observability.jdbcStatement.metric.enabled=true # plugin.observability.jdbcStatement.metric.interval=30 plugin.observability.jdbcStatement.metric.topic=application-metrics plugin.observability.jdbcStatement.metric.url=/application-metrics # plugin.observability.jdbcStatement.metric.appendType=kafka ## jdbcConnection metric # plugin.observability.jdbcConnection.metric.enabled=true # plugin.observability.jdbcConnection.metric.interval=30 plugin.observability.jdbcConnection.metric.topic=application-metrics plugin.observability.jdbcConnection.metric.url=/application-metrics # plugin.observability.jdbcConnection.metric.appendType=kafka ## sql compress ## compress.enabled=true, can use md5Dictionary to compress ## compress.enabled=false, use original sql plugin.observability.jdbc.sql.compress.enabled=true ## md5Dictionary metric # plugin.observability.md5Dictionary.metric.enabled=true # plugin.observability.md5Dictionary.metric.interval=30 plugin.observability.md5Dictionary.metric.topic=application-metrics plugin.observability.md5Dictionary.metric.url=/application-metrics # plugin.observability.md5Dictionary.metric.appendType=kafka ## jdbc redirect # plugin.integrability.jdbc.redirect.enabled=true # # -------------------- kafka --------------------- # kafka tracing # plugin.observability.kafka.tracing.enabled=true # kafka metric # plugin.observability.kafka.metric.enabled=true # plugin.observability.kafka.metric.interval=30 plugin.observability.kafka.metric.topic=platform-metrics plugin.observability.kafka.metric.url=/platform-metrics # plugin.observability.kafka.metric.appendType=kafka # kafka redirect # plugin.integrability.kafka.redirect.enabled=true # # -------------------- rabbitmq --------------------- # rabbitmq tracing # plugin.observability.rabbitmq.tracing.enabled=true # rabbitmq metric # plugin.observability.rabbitmq.metric.enabled=true # plugin.observability.rabbitmq.metric.interval=30 plugin.observability.rabbitmq.metric.topic=platform-metrics plugin.observability.rabbitmq.metric.url=/platform-metrics # plugin.observability.rabbitmq.metric.appendType=kafka # rabbitmq redirect # plugin.integrability.rabbitmq.redirect.enabled=true # # -------------------- redis --------------------- # redis tracing # plugin.observability.redis.tracing.enabled=true # redis metric # plugin.observability.redis.metric.enabled=true # plugin.observability.redis.metric.interval=30 plugin.observability.redis.metric.topic=platform-metrics plugin.observability.redis.metric.url=/platform-metrics # plugin.observability.redis.metric.appendType=kafka # redis redirect # plugin.integrability.redis.redirect.enabled=true # # -------------------- springGateway --------------------- # springGateway tracing # plugin.observability.springGateway.tracing.enabled=true # springGateway metric # plugin.observability.springGateway.metric.enabled=true # plugin.observability.springGateway.metric.interval=30 plugin.observability.springGateway.metric.topic=application-metrics plugin.observability.springGateway.metric.url=/application-metrics # plugin.observability.springGateway.metric.appendType=kafka # # -------------------- request --------------------- ## httpclient tracing:httpclient and httpclient5 # plugin.observability.httpclient.tracing.enabled=true ## okHttp tracing # plugin.observability.okHttp.tracing.enabled=true ## webclient tracing # plugin.observability.webclient.tracing.enabled=true ## feignClient tracing # plugin.observability.feignClient.tracing.enabled=true ## restTemplate tracing # plugin.observability.restTemplate.tracing.enabled=true # # -------------------- access --------------------- ## access: servlet and spring gateway # plugin.observability.access.metric.enabled=true # plugin.observability.access.metric.interval=30 plugin.observability.access.metric.topic=application-log plugin.observability.access.metric.url=/application-log # plugin.observability.access.metric.appendType=kafka # # -------------------- service name --------------------- ## add service name to header by name for easemesh. default name: X-Mesh-RPC-Service # plugin.integrability.serviceName.addServiceNameHead.propagate.head=X-Mesh-RPC-Service # # -------------------- mongodb --------------------- ## mongodb tracing # plugin.observability.mongodb.tracing.enabled=true ## mongodb metric # plugin.observability.mongodb.metric.enabled=true # plugin.observability.mongodb.metric.interval=30 plugin.observability.mongodb.metric.topic=platform-metrics plugin.observability.mongodb.metric.url=/platform-metrics # plugin.observability.mongodb.metric.appendType=kafka ## mongodb redirect # plugin.integrability.mongodb.redirect.enabled=true ## mongodb foundation # plugin.hook.mongodb.foundation.enabled=true # -------------- output ------------------ ## http/kafka/zipkin server host and port for tracing and metric ###### example ###### ## http: [http|https]://127.0.0.1:8080/report ## kafka: 192.168.1.2:9092, 192.168.1.3:9092, 192.168.1.3:9092 ## zipkin: [http|https]://127.0.0.1:8080/zipkin reporter.outputServer.bootstrapServer=127.0.0.1:9092 reporter.outputServer.appendType=console reporter.outputServer.timeout=1000 ## enabled=false: disable output tracing and metric ## enabled=true: output tracing and metric reporter.outputServer.enabled=true ## username and password for http basic auth reporter.outputServer.username= reporter.outputServer.password= ## enable=false: disable mtls ## enable=true: enable tls ## key, cert, ca_cert is enabled when tls.enable=true reporter.outputServer.tls.enable=false reporter.outputServer.tls.key= reporter.outputServer.tls.cert= reporter.outputServer.tls.ca_cert= # --- redefine to output properties reporter.log.output.messageMaxBytes=999900 reporter.log.output.reportThread=1 reporter.log.output.queuedMaxSpans=1000 reporter.log.output.queuedMaxSize=1000000 reporter.log.output.messageTimeout=1000 ## sender.appendType config ## [http] send to http server ## [kafka] send to kafka ## [console] send to console ## reporter.log.sender.appendType=console ## enabled=true: # reporter.log.sender.enabled=true # reporter.log.sender.url=/application-log ## sender.appendType config ## [http] send to http server ## [kafka] send to kafka ## [console] send to console # reporter.tracing.sender.appendType=http # reporter.tracing.sender.appendType=console ## enabled=true: reporter.tracing.sender.enabled=true ## url is only used in http ## append to outputServer.bootstrapServer ###### example ###### ## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report ## reporter.tracing.sender.url=/tracing ## final output url: http://127.0.0.1:8080/report/tracing ## if url is start with [http|https], url override reporter.outputServer.bootstrapServer ###### example ###### ## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report ## reporter.tracing.sender.url=http://127.0.0.10:9090/tracing ## final output url: http://127.0.0.10:9090/tracing reporter.tracing.sender.url=/application-tracing-log ## topic for kafka use reporter.tracing.sender.topic=log-tracing reporter.tracing.encoder=SpanJsonEncoder # --- redefine to output properties reporter.tracing.output.messageMaxBytes=999900 reporter.tracing.output.reportThread=1 reporter.tracing.output.queuedMaxSpans=1000 reporter.tracing.output.queuedMaxSize=1000000 reporter.tracing.output.messageTimeout=1000 ## sender.appendType config ## [http] send to http server ## [metricKafka] send to kafka ## [console] send to console #reporter.metric.sender.appendType=http #reporter.metric.sender.appendType=console ## url is only used in http ## append to outputServer.bootstrapServer ###### example ###### ## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report ## reporter.metric.sender.url=/metric ## final output url: http://127.0.0.1:8080/report/metric ## if url is start with [http|https], url override reporter.outputServer.bootstrapServer ###### example ###### ## reporter.outputServer.bootstrapServer=http://127.0.0.1:8080/report ## reporter.metric.sender.url=http://127.0.0.10:9090/metric ## final output url: http://127.0.0.10:9090/metric #reporter.metric.sender.url=/metrics ## topic for kafka use reporter.metric.sender.topic=application-meter #reporter.metric.encoder=MetricJsonEncoder reporter.metric.output.interval=30 ================================================ FILE: config/src/test/resources/agent.yaml ================================================ name: test-service system: demo-system user: list: - "a" - "b" ### http server easeagent: server: # When the enabled value = false, agent will not start the http server # You can use -Deaseagent.server.enabled=[true | false] to override. enabled: true # http server port. You can use -Deaseagent.server.port=[port] to override. port: 9900 health: readiness: # Enable health/readiness enabled: true # progress: # forwarded: # headers: X-Forwarded-For # headers: X-Location,X-Mesh-Service-Canary,X-Phone-Os ### ### default tracings reporter configuration ### # sampledType: ## counting: percentage sampling, sampled limit 0.01 to 1, 1 is always sample, 0 is never sample, 0.1 is ten samples per hundred ## rate_limiting: traces per second, sampled >= 0, 0 is never sample, 10 is max 10 traces per second ## boundary: percentage sampling by traceId, sampled limit 0.0001 to 1, 1 is always sample, 0 is never sample ## if sampled=0.001, when (traceId^random)%10000<=(0.001*10000) sampled ## sampledType must be used with sampled, otherwise the default value is used Sampler.ALWAYS_SAMPLE # # get header from response headers then tag to tracing span # format: observability.tracings.tag.response.headers.{key}={value} # support ease mesh # X-EG-Circuit-Breaker # X-EG-Retryer # X-EG-Rate-Limiter # X-EG-Time-Limiter observability: tracings: sampledType: "" sampled: 1 tag: response: headers: eg: 0: X-EG-Circuit-Breaker 1: X-EG-Retryer 2: X-EG-Rate-Limiter 3: X-EG-Time-Limiter plugin: hook: global: foundation: enabled: true integrability: global: # add service name to header enabled by name for easemesh addServiceNameHead: enabled: true # forwarded headers enabled. # headers see config: easeagent.progress.forwarded.headers.???=??? forwarded: enabled: true # redirect the middleware address when env has address, see: com.megaease.easeagent.plugin.api.middleware.RedirectProcessor # about redirect: jdbc, kafka, rabbitmq, redis, redirect: enabled: true # -------------------- service name --------------------- ## add service name to header by name for easemesh. default name: X-Mesh-RPC-Service # serviceName: # addServiceNameHead: # propagate: # head: X-Mesh-RPC-Service observability: # -------------------- async --------------------- # async: # tracing: # enabled: true # -------------------- access --------------------- access: metric: # enabled: true # interval: 30 topic: application-log url: /application-log # appendType: kafka # -------------------- elasticsearch redirect --------------------- elasticsearch: metric: # enabled: true # interval: 30 topic: platform-metrics url: /platform-metrics # appendType: kafka # -------------------- plugin global config --------------------- global: metric: enabled: true interval: 30 topic: application-meter ## output by http # appendType: console # appendType: http tracing: enabled: true # -------------------- httpServlet --------------------- httpServlet: metric: # enabled: true # interval: 30 topic: application-metrics url: /application-metrics # appendType: kafka # tracing: # enabled: true # -------------------- request --------------------- ## httpclient tracing, httpclient and httpclient5 # httpclient: # tracing: # enabled: true ## okHttp tracing # okHttp: # tracing: # enabled: true ## webclient tracing # webclient: # tracing: # enabled: true ## feignClient tracing # feignClient: # tracing: # enabled: true ## restTemplate tracing # restTemplate: # tracing: # enabled: true # -------------------- jdbc --------------------- jdbc: # tracing: # enabled: true ## sql compress ## compress.enabled=true, can use md5Dictionary to compress ## compress.enabled=false, use original sql sql: compress: enabled: true ## jdbc redirect # redirect: # enabled: true jdbcConnection: metric: # enabled: true # interval: 30 topic: application-metrics url: /application-metrics # appendType: kafka jdbcStatement: metric: # enabled: true # interval: 30 topic: application-metrics url: /application-metrics # appendType: kafka # ---------------------------------------------- # if the plugin configuration is consistent with the global namespace, # do not add configuration items not commented out in this default configuration file. # otherwise, they can not be overridden by Global configuration in user's configuration file. # -------------------- jvm --------------------- jvmGc: metric: # enabled: true # interval: 30 topic: platform-metrics url: /platform-metrics # appendType: kafka jvmMemory: metric: # enabled: true # interval: 30 topic: platform-metrics url: /platform-metrics # appendType: kafka # -------------------- kafka --------------------- kafka: # kafka tracing # tracing: # enabled: true # kafka metric metric: # enabled: true # interval: 30 topic: platform-metrics url: /platform-metrics # appendType: kafka # kafka redirect # redirect: # enabled: true ## md5Dictionary metric md5Dictionary: metric: # enabled: true # interval: 30 topic: application-metrics url: /application-metrics # appendType: kafka # -------------------- mongodb --------------------- mongodb: ## mongodb tracing # tracing: # enabled: true ## mongodb metric metric: # enabled: true # interval: 30 topic: platform-metrics url: /platform-metrics # appendType: kafka ## mongodb redirect # redirect: # enabled: true ## mongodb foundation # foundation: # enabled: true # -------------------- rabbitmq --------------------- rabbitmq: # rabbitmq tracing # tracing: # enabled: true # rabbitmq metric metric: # enabled: true # interval: 30 topic: platform-metrics url: /platform-metrics # appendType: kafka # rabbitmq redirect # redirect: # enabled: true # -------------------- redis --------------------- redis: # redis tracing # tracing: # enabled: true # redis metric metric: # enabled: true # interval: 30 topic: platform-metrics url: /platform-metrics # redis redirect # redirect: # enabled: true # -------------------- springGateway --------------------- springGateway: # springGateway tracing # tracing: # enabled: true metric: # enabled: true # interval: 30 topic: application-metrics url: /application-metrics # appendType: kafka # -------------- output ------------------ ## http/kafka/zipkin server host and port for tracing and metric ###### example ###### ## http: [http|https]://127.0.0.1:8080/report ## kafka: 192.168.1.2:9092, 192.168.1.3:9092, 192.168.1.3:9092 ## zipkin: [http|https]://127.0.0.1:8080/zipkin reporter: outputServer: appendType: console bootstrapServer: 127.0.0.1:9092 ## enabled=false: disable output tracing and metric ## enabled=true: output tracing and metric enabled: true ## username and password for http basic auth username: '' password: '' timeout: 1000 ## enable=false: disable mtls ## enable=true: enable tls ## key, cert, ca_cert is enabled when tls.enable=true tls: enable: false key: '' cert: '' ca_cert: '' # --- redefine to output properties log: output: messageMaxBytes: 999900 messageTimeout: 1000 queuedMaxSize: 1000000 queuedMaxSpans: 1000 reportThread: 1 ## sender.appendType config ## [http] send to http server ## [kafka] send to kafka ## [console] send to console # sender: # enabled: true # url: /application-log # appendType: console metric: ## reporter.metric.encoder=MetricJsonEncoder output: interval: 30 ## topic for kafka use sender: topic: application-meter tracing: encoder: SpanJsonEncoder # --- redefine to output properties output: messageMaxBytes: 999900 messageTimeout: 1000 queuedMaxSize: 1000000 queuedMaxSpans: 1000 reportThread: 1 ## sender.appendType config ## [http] send to http server ## [kafka] send to kafka ## [console] send to console sender: enabled: true ## topic for kafka use topic: log-tracing url: /application-tracing-log # appendType: http # appendType: console ================================================ FILE: config/src/test/resources/user-spec.properties ================================================ name=user-spec system=system-spec ================================================ FILE: config/src/test/resources/user-spec2.properties ================================================ name=user-spec2 system=system-spec2 ================================================ FILE: config/src/test/resources/user.properties ================================================ name=demo-user system=demo-system ## topic for kafka use reporter.metric.sender.topic=application-meter #reporter.metric.encoder=MetricJsonEncoder reporter.metric.output.interval=30 ================================================ FILE: context/pom.xml ================================================ easeagent com.megaease.easeagent 2.3.0 4.0.0 context com.megaease.easeagent plugin-api ${project.version} com.megaease.easeagent config ${project.version} com.megaease.easeagent log4j2-api com.megaease.easeagent log4j2-mock ${project.version} test com.megaease.easeagent config-mock ${project.version} test ================================================ FILE: context/src/main/java/com/megaease/easeagent/context/AsyncContextImpl.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import com.megaease.easeagent.plugin.api.Cleaner; import com.megaease.easeagent.plugin.api.InitializeContext; import com.megaease.easeagent.plugin.api.context.AsyncContext; import com.megaease.easeagent.plugin.api.trace.SpanContext; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; public class AsyncContextImpl implements AsyncContext { private final SpanContext spanContext; private final Map context; private final Supplier supplier; private AsyncContextImpl(SpanContext spanContext, Map context, Supplier supplier) { this.spanContext = Objects.requireNonNull(spanContext, "spanContext must not be null"); this.context = Objects.requireNonNull(context, "context must not be null"); this.supplier = Objects.requireNonNull(supplier, "supplier must not be null"); } public static AsyncContextImpl build(SpanContext spanContext, Supplier supplier, Map context) { Map contextMap = context == null ? new HashMap<>() : new HashMap<>(context); return new AsyncContextImpl(spanContext, contextMap, supplier); } @Override public boolean isNoop() { return false; } @Override public SpanContext getSpanContext() { return spanContext; } @Override public Cleaner importToCurrent() { return supplier.get().importAsync(this); } @Override public Map getAll() { return context; } @Override @SuppressWarnings("unchecked") public T get(Object o) { return (T) this.context.get(o); } @Override @SuppressWarnings("unchecked") public V put(Object key, V value) { return (V) this.context.put(key, value); } } ================================================ FILE: context/src/main/java/com/megaease/easeagent/context/ContextManager.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import com.megaease.easeagent.config.Configs; import com.megaease.easeagent.config.PluginConfigManager; import com.megaease.easeagent.context.log.LoggerFactoryImpl; import com.megaease.easeagent.context.log.LoggerMdc; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.api.InitializeContext; import com.megaease.easeagent.plugin.api.Reporter; import com.megaease.easeagent.plugin.api.config.IPluginConfig; import com.megaease.easeagent.plugin.api.context.IContextManager; import com.megaease.easeagent.plugin.api.logging.ILoggerFactory; import com.megaease.easeagent.plugin.api.logging.Mdc; import com.megaease.easeagent.plugin.api.metric.MetricProvider; import com.megaease.easeagent.plugin.api.metric.MetricRegistry; import com.megaease.easeagent.plugin.api.metric.MetricRegistrySupplier; import com.megaease.easeagent.plugin.api.metric.name.NameFactory; import com.megaease.easeagent.plugin.api.metric.name.Tags; import com.megaease.easeagent.plugin.api.trace.ITracing; import com.megaease.easeagent.plugin.api.trace.TracingProvider; import com.megaease.easeagent.plugin.api.trace.TracingSupplier; import com.megaease.easeagent.plugin.bridge.*; import com.megaease.easeagent.plugin.utils.NoNull; import javax.annotation.Nonnull; import java.util.function.Supplier; public class ContextManager implements IContextManager { private static final Logger LOGGER = LoggerFactory.getLogger(ContextManager.class.getName()); private static final ThreadLocal LOCAL_SESSION_CONTEXT = ThreadLocal.withInitial(SessionContext::new); private final PluginConfigManager pluginConfigManager; private final Supplier sessionContextSupplier; private final GlobalContext globalContext; private volatile TracingSupplier tracingSupplier = (supplier) -> null; private volatile MetricRegistrySupplier metric = NoOpMetrics.NO_OP_METRIC_SUPPLIER; private ContextManager(@Nonnull Configs conf, @Nonnull PluginConfigManager pluginConfigManager, @Nonnull ILoggerFactory loggerFactory, @Nonnull Mdc mdc) { this.pluginConfigManager = pluginConfigManager; this.sessionContextSupplier = new SessionContextSupplier(); this.globalContext = new GlobalContext(conf, new MetricRegistrySupplierImpl(), loggerFactory, mdc); } public static ContextManager build(Configs conf) { LOGGER.info("build context manager."); ProgressFieldsManager.init(conf); PluginConfigManager pluginConfigManager = PluginConfigManager.builder(conf).build(); LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build(); ILoggerFactory iLoggerFactory = NoOpLoggerFactory.INSTANCE; Mdc mdc = NoOpLoggerFactory.NO_OP_MDC_INSTANCE; if (loggerFactory != null) { iLoggerFactory = loggerFactory; mdc = new LoggerMdc(loggerFactory.factory().mdc()); } ContextManager contextManager = new ContextManager(conf, pluginConfigManager, iLoggerFactory, mdc); EaseAgent.loggerFactory = contextManager.globalContext.getLoggerFactory(); EaseAgent.loggerMdc = contextManager.globalContext.getMdc(); EaseAgent.initializeContextSupplier = contextManager; EaseAgent.metricRegistrySupplier = contextManager.globalContext.getMetric(); EaseAgent.configFactory = contextManager.pluginConfigManager; return contextManager; } @Override public InitializeContext getContext() { return this.sessionContextSupplier.get(); } public void setTracing(@Nonnull TracingProvider tracing) { LOGGER.info("set tracing supplier function."); this.tracingSupplier = tracing.tracingSupplier(); } public void setMetric(@Nonnull MetricProvider metricProvider) { LOGGER.info("set metric supplier function."); this.metric = metricProvider.metricSupplier(); } private class SessionContextSupplier implements Supplier { @Override public InitializeContext get() { SessionContext context = LOCAL_SESSION_CONTEXT.get(); ITracing tracing = context.getTracing(); if (tracing == null || tracing.isNoop()) { context.setCurrentTracing(NoNull.of(tracingSupplier.get(this), NoOpTracer.NO_OP_TRACING)); } if (context.getSupplier() == null) { context.setSupplier(this); } return context; } } public class MetricRegistrySupplierImpl implements MetricRegistrySupplier { @Override public MetricRegistry newMetricRegistry(IPluginConfig config, NameFactory nameFactory, Tags tags) { return NoNull.of(metric.newMetricRegistry(config, nameFactory, tags), NoOpMetrics.NO_OP_METRIC); } @Override public Reporter reporter(IPluginConfig config) { return NoNull.of(metric.reporter(config), NoOpReporter.NO_OP_REPORTER); } } } ================================================ FILE: context/src/main/java/com/megaease/easeagent/context/GlobalContext.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import com.megaease.easeagent.config.Configs; import com.megaease.easeagent.plugin.api.logging.ILoggerFactory; import com.megaease.easeagent.plugin.api.logging.Mdc; import com.megaease.easeagent.plugin.api.metric.MetricRegistrySupplier; import javax.annotation.Nonnull; public class GlobalContext { private final Configs conf; private final MetricRegistrySupplier metric; private final ILoggerFactory loggerFactory; private final Mdc mdc; public GlobalContext(@Nonnull Configs conf, @Nonnull MetricRegistrySupplier metric, @Nonnull ILoggerFactory loggerFactory, @Nonnull Mdc mdc) { this.conf = conf; this.metric = metric; this.loggerFactory = loggerFactory; this.mdc = mdc; } public Configs getConf() { return conf; } public Mdc getMdc() { return mdc; } public ILoggerFactory getLoggerFactory() { return loggerFactory; } public MetricRegistrySupplier getMetric() { return metric; } } ================================================ FILE: context/src/main/java/com/megaease/easeagent/context/ProgressFieldsManager.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import com.megaease.easeagent.config.Configs; import com.megaease.easeagent.plugin.api.ProgressFields; import com.megaease.easeagent.plugin.api.config.ChangeItem; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; public class ProgressFieldsManager { private ProgressFieldsManager() { } public static void init(Configs configs) { Consumer> changeListener = ProgressFields.changeListener(); changeListener.accept(configs.getConfigs()); configs.addChangeListener(list -> { Map map = new HashMap<>(); for (ChangeItem changeItem : list) { String key = changeItem.getFullName(); if (ProgressFields.isProgressFields(key)) { map.put(key, changeItem.getNewValue()); } changeListener.accept(map); } }); } } ================================================ FILE: context/src/main/java/com/megaease/easeagent/context/RetBound.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import java.util.HashMap; import java.util.Map; public class RetBound { int size; Map local; RetBound(int size) { this.size = size; } public int size() { return this.size; } public Object get(String key) { if (local == null) { return null; } return local.get(key); } public void put(String key, Object value) { if (local == null) { this.local = new HashMap<>(); } this.local.put(key, value); } } ================================================ FILE: context/src/main/java/com/megaease/easeagent/context/SessionContext.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.api.Cleaner; import com.megaease.easeagent.plugin.api.InitializeContext; import com.megaease.easeagent.plugin.api.ProgressFields; import com.megaease.easeagent.plugin.api.config.IPluginConfig; import com.megaease.easeagent.plugin.api.context.AsyncContext; import com.megaease.easeagent.plugin.api.context.RequestContext; import com.megaease.easeagent.plugin.api.trace.*; import com.megaease.easeagent.plugin.bridge.NoOpCleaner; import com.megaease.easeagent.plugin.bridge.NoOpIPluginConfig; import com.megaease.easeagent.plugin.bridge.NoOpTracer; import com.megaease.easeagent.plugin.field.NullObject; import com.megaease.easeagent.plugin.utils.NoNull; import java.util.*; import java.util.function.Supplier; @SuppressWarnings("unused, unchecked") public class SessionContext implements InitializeContext { private static final Logger LOGGER = LoggerFactory.getLogger(SessionContext.class); private static final Setter NOOP_SETTER = (name, value) -> { }; private ITracing tracing = NoOpTracer.NO_OP_TRACING; private Supplier supplier; private final Deque configs = new ArrayDeque<>(); private final Deque retStack = new ArrayDeque<>(); private final Deque retBound = new ArrayDeque<>(); private final Map context = new HashMap<>(); private final Map entered = new HashMap<>(); private boolean hasCleaner = false; @Override public boolean isNoop() { return false; } public Supplier getSupplier() { return supplier; } public void setSupplier(Supplier supplier) { this.supplier = supplier; } @Override public Tracing currentTracing() { return NoNull.of(tracing, NoOpTracer.NO_OP_TRACING); } @Override public V get(Object key) { return change(context.get(key)); } @Override public V remove(Object key) { return change(context.remove(key)); } @SuppressWarnings("unchecked") private V change(Object o) { return o == null ? null : (V) o; } @Override public V put(Object key, V value) { context.put(key, value); return value; } @Override public V putLocal(String key, V value) { assert this.retBound.peek() != null; this.retBound.peek().put(key, value); return value; } @Override public V getLocal(String key) { assert this.retBound.peek() != null; return change(this.retBound.peek().get(key)); } @Override public IPluginConfig getConfig() { if (configs.isEmpty()) { LOGGER.warn("context.configs was empty."); return NoOpIPluginConfig.INSTANCE; } return configs.peek(); } @Override public void pushConfig(IPluginConfig config) { configs.push(config); } @Override public IPluginConfig popConfig() { if (configs.isEmpty()) { LOGGER.warn("context.configs was empty."); return NoOpIPluginConfig.INSTANCE; } return configs.pop(); } @Override public int enter(Object key) { Integer count = entered.get(key); if (count == null) { count = 1; } else { count++; } entered.put(key, count); return count; } @Override public int exit(Object key) { Integer count = entered.get(key); if (count == null) { return 0; } entered.put(key, count - 1); return count; } @Override public AsyncContext exportAsync() { return AsyncContextImpl.build(tracing.exportAsync(), supplier, context); } @Override public Cleaner importAsync(AsyncContext snapshot) { Scope scope = tracing.importAsync(snapshot.getSpanContext()); context.putAll(snapshot.getAll()); if (hasCleaner) { return new AsyncCleaner(scope, false); } else { hasCleaner = true; return new AsyncCleaner(scope, true); } } @Override public RequestContext clientRequest(Request request) { return tracing.clientRequest(request); } @Override public RequestContext serverReceive(Request request) { return tracing.serverReceive(request); } @Override public Span consumerSpan(MessagingRequest request) { return tracing.consumerSpan(request); } @Override public Span producerSpan(MessagingRequest request) { return tracing.producerSpan(request); } @Override public Span nextSpan() { return tracing.nextSpan(); } /** * called by framework to maintain stack */ @Override @SuppressWarnings("ConstantConditions") public void popToBound() { while (this.retStack.size() > this.retBound.peek().size()) { this.retStack.pop(); } } /** * called by framework to maintain stack */ public void pushRetBound() { this.retBound.push(new RetBound(this.retStack.size())); } /** * called by framework to maintain stack */ public void popRetBound() { this.retBound.pop(); } @Override public void push(T obj) { if (obj == null) { this.retStack.push(NullObject.NULL); } else { this.retStack.push(obj); } } @Override @SuppressWarnings("ConstantConditions") public T pop() { if (this.retStack.size() <= this.retBound.peek().size()) { return null; } Object o = this.retStack.pop(); if (o == NullObject.NULL) { return null; } return change(o); } @Override public T peek() { if (this.retStack.isEmpty()) { return null; } Object o = this.retStack.pop(); if (o == NullObject.NULL) { return null; } return change(o); } @Override public Runnable wrap(Runnable task) { return new CurrentContextRunnable(exportAsync(), task); } @Override public boolean isWrapped(Runnable task) { return task instanceof CurrentContextRunnable; } @Override public boolean isNecessaryKeys(String key) { return tracing.propagationKeys().contains(key); } @Override public void consumerInject(Span span, MessagingRequest request) { Injector injector = tracing.messagingTracing().consumerInjector(); injector.inject(span, request); } @Override public void producerInject(Span span, MessagingRequest request) { Injector injector = tracing.messagingTracing().producerInjector(); injector.inject(span, request); } @Override public void injectForwardedHeaders(Setter setter) { Set fields = ProgressFields.getForwardedHeaders(); if (fields.isEmpty()) { return; } for (String field : fields) { Object o = context.get(field); if ((o instanceof String)) { setter.setHeader(field, (String) o); } } } @Override public Cleaner importForwardedHeaders(Getter getter) { return importForwardedHeaders(getter, NOOP_SETTER); } private Cleaner importForwardedHeaders(Getter getter, Setter setter) { Set fields = ProgressFields.getForwardedHeaders(); if (fields.isEmpty()) { return NoOpCleaner.INSTANCE; } List fieldArr = new ArrayList<>(fields.size()); for (String field : fields) { String o = getter.header(field); if (o == null) { continue; } fieldArr.add(field); this.context.put(field, o); setter.setHeader(field, o); } if (fieldArr.isEmpty()) { return NoOpCleaner.INSTANCE; } return new FieldCleaner(fieldArr); } public ITracing getTracing() { return tracing; } @Override public void setCurrentTracing(ITracing tracing) { this.tracing = NoNull.of(tracing, NoOpTracer.NO_OP_TRACING); } @Override public void clear() { if (!this.configs.isEmpty()) { this.configs.clear(); } if (!this.retStack.isEmpty()) { this.retStack.clear(); } if (!this.retBound.isEmpty()) { this.retBound.clear(); } if (!this.context.isEmpty()) { this.context.clear(); } if (!this.entered.isEmpty()) { this.entered.clear(); } this.hasCleaner = false; } public static class CurrentContextRunnable implements Runnable { private final AsyncContext asyncContext; private final Runnable task; public CurrentContextRunnable(AsyncContext asyncContext, Runnable task) { this.asyncContext = asyncContext; this.task = task; } @Override public void run() { try (Cleaner cleaner = asyncContext.importToCurrent()) { task.run(); } } } private class FieldCleaner implements Cleaner { private final List fields; public FieldCleaner(List fields) { this.fields = fields; } @Override public void close() { for (String field : fields) { context.remove(field); } } } public class AsyncCleaner implements Cleaner { private final Scope scope; private final boolean clearContext; public AsyncCleaner(Scope scope, boolean clearContext) { this.scope = scope; this.clearContext = clearContext; } @Override public void close() { this.scope.close(); if (clearContext) { SessionContext.this.clear(); } } } } ================================================ FILE: context/src/main/java/com/megaease/easeagent/context/log/LoggerFactoryImpl.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context.log; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.log4j2.api.AgentLoggerFactory; import com.megaease.easeagent.plugin.api.logging.ILoggerFactory; import com.megaease.easeagent.plugin.api.logging.Logger; import javax.annotation.Nonnull; public class LoggerFactoryImpl implements ILoggerFactory { private final AgentLoggerFactory loggerFactory; private LoggerFactoryImpl(@Nonnull AgentLoggerFactory loggerFactory) { this.loggerFactory = loggerFactory; } @Override public Logger getLogger(String name) { return loggerFactory.getLogger(name); } public AgentLoggerFactory factory() { return loggerFactory; } public static LoggerFactoryImpl build() { AgentLoggerFactory loggerFactory = LoggerFactory.newFactory(LoggerImpl.LOGGER_SUPPLIER, LoggerImpl.class); if (loggerFactory == null) { return null; } return new LoggerFactoryImpl(loggerFactory); } } ================================================ FILE: context/src/main/java/com/megaease/easeagent/context/log/LoggerImpl.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context.log; import com.megaease.easeagent.log4j2.api.AgentLogger; import java.util.function.Function; import java.util.logging.Logger; public class LoggerImpl extends AgentLogger implements com.megaease.easeagent.plugin.api.logging.Logger { public static final Function LOGGER_SUPPLIER = LoggerImpl::new; public LoggerImpl(Logger logger) { super(logger); } } ================================================ FILE: context/src/main/java/com/megaease/easeagent/context/log/LoggerMdc.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context.log; import com.megaease.easeagent.plugin.api.logging.Mdc; public class LoggerMdc implements Mdc { private final com.megaease.easeagent.log4j2.api.Mdc mdc; public LoggerMdc(com.megaease.easeagent.log4j2.api.Mdc mdc) { this.mdc = mdc; } @Override public void put(String key, String value) { mdc.put(key, value); } @Override public void remove(String key) { mdc.remove(key); } @Override public String get(String key) { return mdc.get(key); } } ================================================ FILE: context/src/test/java/com/megaease/easeagent/context/AsyncContextImplTest.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import com.megaease.easeagent.plugin.api.Cleaner; import com.megaease.easeagent.plugin.bridge.NoOpTracer; import org.junit.Test; import java.util.Collections; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.*; public class AsyncContextImplTest { @Test public void build() { try { AsyncContextImpl.build(null, null, null); assertTrue("must be throw error", false); } catch (Exception e) { assertNotNull(e); } try { AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, null, null); assertTrue("must be throw error", false); } catch (Exception e) { assertNotNull(e); } AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, null); assertNotNull(asyncContext.getAll()); } @Test public void isNoop() { AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, null); assertFalse(asyncContext.isNoop()); } @Test public void getSpanContext() { AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, null); assertTrue(asyncContext.getSpanContext().isNoop()); } @Test public void importToCurrent() { String name = "test_name"; String value = "test_value"; SessionContext sessionContext = new SessionContext(); AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> sessionContext, null); asyncContext.put(name, value); assertNull(sessionContext.get(name)); try (Cleaner cleaner = asyncContext.importToCurrent()) { assertEquals(value, sessionContext.get(name)); } assertNull(sessionContext.get(name)); AsyncContextImpl asyncContext2 = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> sessionContext, Collections.singletonMap(name, value)); assertNull(sessionContext.get(name)); try (Cleaner cleaner = asyncContext2.importToCurrent()) { assertEquals(value, sessionContext.get(name)); } assertNull(sessionContext.get(name)); } @Test public void getAll() { String name = "test_name"; String value = "test_value"; AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, Collections.singletonMap(name, value)); assertNotNull(asyncContext.getAll()); assertEquals(value, asyncContext.getAll().get(name)); } @Test public void get() { String name = "test_name"; String value = "test_value"; AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, Collections.singletonMap(name, value)); String v = asyncContext.get(name); assertNotNull(v); assertEquals(v, value); assertNull(asyncContext.get(name + "test")); } @Test public void put() { Map context = new HashMap<>(); String name = "test_name"; String value = "test_value"; String name2 = name + "2"; context.put(name, value); AsyncContextImpl asyncContext = AsyncContextImpl.build(NoOpTracer.NO_OP_SPAN_CONTEXT, () -> null, context); asyncContext.put(name2, value); assertNull(context.get(name2)); assertEquals(value, asyncContext.get(name)); assertEquals(value, asyncContext.get(name2)); } } ================================================ FILE: context/src/test/java/com/megaease/easeagent/context/ContextManagerTest.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import com.megaease.easeagent.mock.config.MockConfig; import com.megaease.easeagent.plugin.api.Reporter; import com.megaease.easeagent.plugin.api.config.IPluginConfig; import com.megaease.easeagent.plugin.api.metric.MetricRegistry; import com.megaease.easeagent.plugin.api.metric.MetricRegistrySupplier; import com.megaease.easeagent.plugin.api.metric.name.NameFactory; import com.megaease.easeagent.plugin.api.metric.name.Tags; import org.junit.Test; import static org.junit.Assert.assertNotNull; public class ContextManagerTest { @Test public void build() { ContextManager contextManager = ContextManager.build(MockConfig.getCONFIGS()); assertNotNull(contextManager); contextManager.setTracing(() -> contextSupplier -> null); } @Test public void setTracing() { ContextManager contextManager = ContextManager.build(MockConfig.getCONFIGS()); assertNotNull(contextManager); contextManager.setTracing(() -> contextSupplier -> null); } @Test public void setMetric() { ContextManager contextManager = ContextManager.build(MockConfig.getCONFIGS()); assertNotNull(contextManager); contextManager.setMetric(() -> new MetricRegistrySupplier() { @Override public MetricRegistry newMetricRegistry(IPluginConfig config, NameFactory nameFactory, Tags tags) { return null; } @Override public Reporter reporter(IPluginConfig config) { return null; } }); } } ================================================ FILE: context/src/test/java/com/megaease/easeagent/context/GlobalContextTest.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import com.megaease.easeagent.config.Configs; import com.megaease.easeagent.context.log.LoggerFactoryImpl; import com.megaease.easeagent.context.log.LoggerMdc; import com.megaease.easeagent.plugin.bridge.NoOpMetrics; import org.junit.Before; import org.junit.Test; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertNotNull; public class GlobalContextTest { GlobalContext globalContext; @Before public void before() { Map initConfigs = new HashMap<>(); initConfigs.put("name", "demo-service"); initConfigs.put("system", "demo-system"); Configs configs = new Configs(initConfigs); LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build(); globalContext = new GlobalContext(configs, NoOpMetrics.NO_OP_METRIC_SUPPLIER, loggerFactory, new LoggerMdc(loggerFactory.factory().mdc())); } @Test public void getConf() { assertNotNull(globalContext.getConf()); } @Test public void getMdc() { assertNotNull(globalContext.getMdc()); } @Test public void getLoggerFactory() { assertNotNull(globalContext.getLoggerFactory()); } @Test public void getMetric() { assertNotNull(globalContext.getMetric()); } } ================================================ FILE: context/src/test/java/com/megaease/easeagent/context/ProgressFieldsManagerTest.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import com.megaease.easeagent.config.Configs; import com.megaease.easeagent.plugin.api.ProgressFields; import org.junit.Test; import java.util.Collections; import java.util.HashMap; import java.util.Set; import static com.megaease.easeagent.plugin.api.ProgressFields.EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class ProgressFieldsManagerTest { @Test public void init() { HashMap source = new HashMap<>(); source.put("plugin.observability.global.metrics.enabled", "true"); source.put(EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG, "aaa,bbb,ccc"); Configs configs = new Configs(source); ProgressFieldsManager.init(configs); Set fields = ProgressFields.getForwardedHeaders(); assertFalse(fields.isEmpty()); } @Test public void isEmpty() { assertTrue(true); assertTrue(ProgressFields.isEmpty(new String[0])); assertFalse(ProgressFields.isEmpty(new String[1])); } @Test public void getFields() { HashMap source = new HashMap<>(); source.put("plugin.observability.global.metrics.enabled", "true"); source.put(EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG, "a,b,c"); Configs configs = new Configs(source); ProgressFieldsManager.init(configs); Set fields = ProgressFields.getForwardedHeaders(); assertFalse(fields.isEmpty()); assertTrue(fields.contains("a")); assertTrue(fields.contains("b")); assertTrue(fields.contains("c")); configs.updateConfigs(Collections.singletonMap(EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG, "a")); configs.updateConfigs(Collections.singletonMap(EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG, "c")); fields = ProgressFields.getForwardedHeaders(); assertFalse(fields.isEmpty()); assertFalse(fields.contains("a")); assertFalse(fields.contains("b")); assertTrue(fields.contains("c")); } } ================================================ FILE: context/src/test/java/com/megaease/easeagent/context/RetBoundTest.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import org.junit.Test; import static org.junit.Assert.*; public class RetBoundTest { @Test public void size() { assertEquals(100, new RetBound(100).size()); } @Test public void get() { RetBound retBound = new RetBound(1); Object o = new Object(); Object o2 = new Object(); retBound.put("a", o); assertEquals(o, retBound.get("a")); assertNotEquals(o2, retBound.get("a")); } @Test public void put() { get(); } } ================================================ FILE: context/src/test/java/com/megaease/easeagent/context/SessionContextTest.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context; import com.megaease.easeagent.config.PluginConfig; import com.megaease.easeagent.config.PluginConfigManager; import com.megaease.easeagent.mock.config.MockConfig; import com.megaease.easeagent.plugin.api.Cleaner; import com.megaease.easeagent.plugin.api.InitializeContext; import com.megaease.easeagent.plugin.api.ProgressFields; import com.megaease.easeagent.plugin.api.config.IPluginConfig; import com.megaease.easeagent.plugin.api.context.AsyncContext; import com.megaease.easeagent.plugin.api.context.RequestContext; import com.megaease.easeagent.plugin.api.trace.*; import com.megaease.easeagent.plugin.bridge.EaseAgent; import com.megaease.easeagent.plugin.bridge.NoOpIPluginConfig; import com.megaease.easeagent.plugin.bridge.NoOpTracer; import org.junit.Before; import org.junit.Test; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import static com.megaease.easeagent.plugin.api.ProgressFields.EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG; import static org.junit.Assert.*; public class SessionContextTest { @Before public void before() { ContextManager contextManager = ContextManager.build(MockConfig.getCONFIGS()); assertNotNull(contextManager); } @Test public void isNoop() { SessionContext sessionContext = new SessionContext(); assertFalse(sessionContext.isNoop()); } @Test public void getSupplier() { SessionContext sessionContext = new SessionContext(); assertNull(sessionContext.getSupplier()); sessionContext.setSupplier(() -> null); assertNotNull(sessionContext.getSupplier()); } @Test public void setSupplier() { getSupplier(); } @Test public void currentTracing() { SessionContext sessionContext = new SessionContext(); assertTrue(sessionContext.currentTracing().isNoop()); sessionContext.setCurrentTracing(new MockITracing()); assertNotNull(sessionContext.currentTracing()); assertFalse(sessionContext.currentTracing().isNoop()); } @Test public void get() { String name = "test_name"; String value = "test_value"; SessionContext sessionContext = new SessionContext(); assertNull(sessionContext.get(name)); sessionContext.put(name, value); assertEquals(value, sessionContext.get(name)); sessionContext.remove(name); assertNull(sessionContext.get(name)); } @Test public void remove() { get(); } @Test public void put() { get(); } @Test public void getLocal() { //Deprecated } @Test public void putLocal() { //Deprecated } @Test public void getConfig() { SessionContext sessionContext = new SessionContext(); assertNotNull(sessionContext.getConfig()); IPluginConfig iPluginConfig = sessionContext.getConfig(); assertEquals(NoOpIPluginConfig.INSTANCE.domain(), iPluginConfig.domain()); assertEquals(NoOpIPluginConfig.INSTANCE.namespace(), iPluginConfig.namespace()); assertEquals(NoOpIPluginConfig.INSTANCE.id(), iPluginConfig.id()); sessionContext.pushConfig(NoOpIPluginConfig.INSTANCE); assertNotNull(sessionContext.getConfig()); PluginConfigManager pluginConfigManager = PluginConfigManager.builder(MockConfig.getCONFIGS()).build(); String domain = "observability"; String namespace = "test_config"; String id = "test"; PluginConfig config = pluginConfigManager.getConfig(domain, namespace, id); sessionContext.pushConfig(config); iPluginConfig = sessionContext.getConfig(); assertEquals(domain, iPluginConfig.domain()); assertEquals(namespace, iPluginConfig.namespace()); assertEquals(id, iPluginConfig.id()); IPluginConfig oppConfig = sessionContext.popConfig(); assertEquals(domain, oppConfig.domain()); assertEquals(namespace, oppConfig.namespace()); assertEquals(id, oppConfig.id()); iPluginConfig = sessionContext.getConfig(); assertEquals(NoOpIPluginConfig.INSTANCE.domain(), iPluginConfig.domain()); assertEquals(NoOpIPluginConfig.INSTANCE.namespace(), iPluginConfig.namespace()); assertEquals(NoOpIPluginConfig.INSTANCE.id(), iPluginConfig.id()); } @Test public void pushConfig() { getConfig(); } @Test public void popConfig() { getConfig(); } @Test public void enter() { SessionContext sessionContext = new SessionContext(); Object key1 = new Object(); Object key2 = new Object(); assertEquals(1, sessionContext.enter(key1)); assertEquals(1, sessionContext.enter(key2)); assertEquals(2, sessionContext.enter(key1)); assertEquals(2, sessionContext.enter(key2)); assertEquals(2, sessionContext.exit(key1)); assertEquals(2, sessionContext.exit(key2)); assertEquals(1, sessionContext.exit(key1)); assertEquals(1, sessionContext.exit(key2)); assertTrue(sessionContext.enter(key1, 1)); assertTrue(sessionContext.enter(key2, 1)); assertTrue(sessionContext.enter(key1, 2)); assertTrue(sessionContext.enter(key2, 2)); assertTrue(sessionContext.exit(key1, 2)); assertTrue(sessionContext.exit(key2, 2)); assertTrue(sessionContext.exit(key1, 1)); assertTrue(sessionContext.exit(key2, 1)); } @Test public void exit() { enter(); } @Test public void exportAsync() { SessionContext sessionContext = new SessionContext(); sessionContext.setSupplier(() -> EaseAgent.initializeContextSupplier.getContext()); MockITracing iTracing = new MockITracing(); sessionContext.setCurrentTracing(iTracing); String name = "test_name"; String value = "test_value"; assertNull(sessionContext.get(name)); sessionContext.put(name, value); assertEquals(value, sessionContext.get(name)); AsyncContext asyncContext = sessionContext.exportAsync(); assertTrue(asyncContext.getSpanContext().isNoop()); assertEquals(1, iTracing.exportAsyncCount.get()); assertEquals(value, asyncContext.get(name)); assertFalse(asyncContext.isNoop()); SessionContext sessionContext2 = new SessionContext(); sessionContext2.setCurrentTracing(iTracing); assertNull(sessionContext2.get(name)); try (Cleaner ignored = sessionContext2.importAsync(asyncContext)) { assertEquals(value, sessionContext2.get(name)); assertEquals(1, iTracing.importAsyncCount.get()); try (Cleaner ignored1 = sessionContext2.importAsync(asyncContext)) { assertEquals(2, iTracing.importAsyncCount.get()); } assertEquals(value, sessionContext2.get(name)); } } @Test public void importAsync() { exportAsync(); } @Test public void clientRequest() { SessionContext sessionContext = new SessionContext(); MockITracing iTracing = new MockITracing(); sessionContext.setCurrentTracing(iTracing); sessionContext.clientRequest(new EmptyRequest()); assertEquals(1, iTracing.clientRequestCount.get()); } @Test public void serverReceive() { SessionContext sessionContext = new SessionContext(); MockITracing iTracing = new MockITracing(); sessionContext.setCurrentTracing(iTracing); sessionContext.serverReceive(new EmptyRequest()); assertEquals(1, iTracing.serverReceiveCount.get()); } @Test public void consumerSpan() { SessionContext sessionContext = new SessionContext(); MockITracing iTracing = new MockITracing(); sessionContext.setCurrentTracing(iTracing); sessionContext.consumerSpan(new EmptyRequest()); assertEquals(1, iTracing.consumerSpanCount.get()); } @Test public void producerSpan() { SessionContext sessionContext = new SessionContext(); MockITracing iTracing = new MockITracing(); sessionContext.setCurrentTracing(iTracing); sessionContext.producerSpan(new EmptyRequest()); assertEquals(1, iTracing.producerSpanCount.get()); } @Test public void nextSpan() { SessionContext sessionContext = new SessionContext(); MockITracing iTracing = new MockITracing(); sessionContext.setCurrentTracing(iTracing); sessionContext.nextSpan(); assertEquals(1, iTracing.nextSpanCount.get()); } @Test public void popToBound() { //Deprecated } @Test public void pushRetBound() { //Deprecated } @Test public void popRetBound() { //Deprecated } @Test public void push() { //Deprecated } @Test public void pop() { //Deprecated } @Test public void peek() { //Deprecated } @Test public void wrap() throws InterruptedException { SessionContext sessionContext = new SessionContext(); MockITracing iTracing = new MockITracing(); sessionContext.setCurrentTracing(iTracing); String name = "test_name"; String value = "test_value"; sessionContext.put(name, value); AtomicReference lastSessionContext = new AtomicReference<>(); Supplier newContextSupplier = () -> { SessionContext sessionContext1 = new SessionContext(); sessionContext1.setCurrentTracing(iTracing); lastSessionContext.set(sessionContext1); return sessionContext1; }; sessionContext.setSupplier(newContextSupplier); Runnable runnable = () -> { assertNotNull(lastSessionContext.get()); assertEquals(value, lastSessionContext.get().get(name)); }; runnable = sessionContext.wrap(runnable); assertTrue(sessionContext.isWrapped(runnable)); Thread thread = new Thread(runnable); thread.start(); thread.join(); assertNull(lastSessionContext.get().get(name)); assertEquals(1, iTracing.exportAsyncCount.get()); assertEquals(1, iTracing.importAsyncCount.get()); } @Test public void isWrapped() throws InterruptedException { wrap(); } @Test public void isNecessaryKeys() { SessionContext sessionContext = new SessionContext(); MockITracing iTracing = new MockITracing(); iTracing.setPropagationKeys(Collections.singletonList("b3")); sessionContext.setCurrentTracing(iTracing); assertTrue(sessionContext.isNecessaryKeys("b3")); assertFalse(sessionContext.isNecessaryKeys("aaa")); } @Test public void consumerInject() { SessionContext sessionContext = new SessionContext(); MockITracing iTracing = new MockITracing(); sessionContext.setCurrentTracing(iTracing); sessionContext.consumerInject(NoOpTracer.NO_OP_SPAN, new EmptyRequest()); assertEquals(1, iTracing.mockMessagingTracing.consumerInjectorCount.get()); } @Test public void producerInject() { SessionContext sessionContext = new SessionContext(); MockITracing iTracing = new MockITracing(); sessionContext.setCurrentTracing(iTracing); sessionContext.producerInject(NoOpTracer.NO_OP_SPAN, new EmptyRequest()); assertEquals(1, iTracing.mockMessagingTracing.producerInjectorCount.get()); } @Test public void injectForwardedHeaders() { String forwarded = "test_forwarded_value"; String forwardedKey = EASEAGENT_PROGRESS_FORWARDED_HEADERS_CONFIG; String headerValue = "test_value"; ProgressFields.changeListener().accept(Collections.singletonMap(forwardedKey, forwarded)); ProgressFields.changeListener().accept(Collections.singletonMap(forwardedKey + "2", forwarded + "2")); SessionContext sessionContext = new SessionContext(); sessionContext.importForwardedHeaders(name -> { if (name.equals(forwarded)) { return headerValue; } return null; }); Map values = new HashMap<>(); sessionContext.injectForwardedHeaders(values::put); assertEquals(1, values.size()); assertEquals(headerValue, values.get(forwarded)); ProgressFields.changeListener().accept(Collections.singletonMap(forwardedKey, "")); ProgressFields.changeListener().accept(Collections.singletonMap(forwardedKey + "2", "")); } @Test public void getTracing() { SessionContext sessionContext = new SessionContext(); MockITracing iTracing = new MockITracing(); sessionContext.setCurrentTracing(iTracing); assertNotNull(sessionContext.getTracing()); assertTrue(sessionContext.getTracing() instanceof MockITracing); } @Test public void setCurrentTracing() { getTracing(); } @Test public void clear() { String name = "test_name"; String value = "test_value"; SessionContext sessionContext = new SessionContext(); sessionContext.put(name, value); assertEquals(1, sessionContext.enter("test_key")); sessionContext.clear(); assertNull(sessionContext.get(name)); assertEquals(1, sessionContext.enter("test_key")); sessionContext.clear(); } public static class EmptyRequest implements MessagingRequest { @Override public String operation() { return null; } @Override public String channelKind() { return null; } @Override public String channelName() { return null; } @Override public Object unwrap() { return null; } @Override public Span.Kind kind() { return null; } @Override public String header(String name) { return null; } @Override public String name() { return null; } @Override public boolean cacheScope() { return false; } @Override public void setHeader(String name, String value) { } } public static class MockITracing extends NoOpTracer.NoopTracing { public AtomicInteger exportAsyncCount = new AtomicInteger(); public AtomicInteger importAsyncCount = new AtomicInteger(); public AtomicInteger clientRequestCount = new AtomicInteger(); public AtomicInteger serverReceiveCount = new AtomicInteger(); public AtomicInteger consumerSpanCount = new AtomicInteger(); public AtomicInteger producerSpanCount = new AtomicInteger(); public AtomicInteger nextSpanCount = new AtomicInteger(); public MockMessagingTracing mockMessagingTracing = new MockMessagingTracing(); private List propagationKeys = Collections.emptyList(); public MockITracing setPropagationKeys(List propagationKeys) { this.propagationKeys = propagationKeys; return this; } @Override public SpanContext exportAsync() { exportAsyncCount.incrementAndGet(); return super.exportAsync(); } @Override public Scope importAsync(SpanContext snapshot) { importAsyncCount.incrementAndGet(); return super.importAsync(snapshot); } @Override public RequestContext clientRequest(Request request) { clientRequestCount.incrementAndGet(); return super.clientRequest(request); } @Override public RequestContext serverReceive(Request request) { serverReceiveCount.incrementAndGet(); return super.serverReceive(request); } @Override public Span consumerSpan(MessagingRequest request) { consumerSpanCount.incrementAndGet(); return super.consumerSpan(request); } @Override public Span producerSpan(MessagingRequest request) { producerSpanCount.incrementAndGet(); return super.producerSpan(request); } @Override public Span nextSpan() { nextSpanCount.incrementAndGet(); return super.nextSpan(); } @Override public List propagationKeys() { return propagationKeys; } @Override public MessagingTracing messagingTracing() { return mockMessagingTracing; } @Override public boolean isNoop() { return false; } } public static class MockMessagingTracing extends NoOpTracer.EmptyMessagingTracing { public AtomicInteger producerInjectorCount = new AtomicInteger(); public AtomicInteger consumerInjectorCount = new AtomicInteger(); @Override public Injector producerInjector() { return (span, request) -> producerInjectorCount.incrementAndGet(); } @Override public Injector consumerInjector() { return (span, request) -> consumerInjectorCount.incrementAndGet(); } } } ================================================ FILE: context/src/test/java/com/megaease/easeagent/context/log/LoggerFactoryImplTest.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context.log; import com.megaease.easeagent.log4j2.MDC; import com.megaease.easeagent.log4j2.api.AgentLoggerFactory; import com.megaease.easeagent.plugin.api.logging.Logger; import org.junit.Test; import static org.junit.Assert.assertNotNull; public class LoggerFactoryImplTest { LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build(); @Test public void getLogger() { Logger logger = loggerFactory.getLogger(LoggerFactoryImplTest.class.getName()); logger.info("aaaa"); MDC.put("testMdc", "testMdc_value"); logger.info("bbbb"); assertNotNull(MDC.get("testMdc")); } @Test public void factory() { AgentLoggerFactory agentLoggerFactory = loggerFactory.factory(); assertNotNull(agentLoggerFactory); LoggerImpl logger = agentLoggerFactory.getLogger(LoggerFactoryImplTest.class.getName()); logger.info("aaaa"); agentLoggerFactory.mdc().put("newFactory", "newFactory"); assertNotNull(MDC.get("newFactory")); } @Test public void build() { LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build(); assertNotNull(loggerFactory); } } ================================================ FILE: context/src/test/java/com/megaease/easeagent/context/log/LoggerImplTest.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context.log; import com.megaease.easeagent.plugin.api.logging.Logger; import org.junit.Test; import static org.junit.Assert.*; public class LoggerImplTest { @Test public void test() { LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build(); Logger logger = loggerFactory.getLogger(LoggerFactoryImplTest.class.getName()); assertTrue(logger instanceof LoggerImpl); } } ================================================ FILE: context/src/test/java/com/megaease/easeagent/context/log/LoggerMdcTest.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.context.log; import org.junit.Test; import static org.junit.Assert.*; public class LoggerMdcTest { LoggerFactoryImpl loggerFactory = LoggerFactoryImpl.build(); LoggerMdc mdc = new LoggerMdc(loggerFactory.factory().mdc()); @Test public void put() { mdc.put("testA", "testB"); assertNull(org.slf4j.MDC.get("testA")); assertNotNull(mdc.get("testA")); } @Test public void remove() { mdc.put("testA", "testB"); assertNotNull(mdc.get("testA")); mdc.remove("testA"); assertNull(mdc.get("testA")); } @Test public void get() { mdc.put("testA", "testB"); assertNull(org.slf4j.MDC.get("testA")); assertNotNull(mdc.get("testA")); org.slf4j.MDC.put("testB", "testB"); assertNotNull(org.slf4j.MDC.get("testB")); assertNull(mdc.get("testB")); } } ================================================ FILE: context/src/test/resources/log4j2.xml ================================================ ================================================ FILE: core/pom.xml ================================================ easeagent com.megaease.easeagent 2.3.0 4.0.0 core com.google.auto.service auto-service net.bytebuddy byte-buddy com.google.guava guava com.megaease.easeagent plugin-api ${project.version} com.megaease.easeagent context ${project.version} com.megaease.easeagent config ${project.version} com.megaease.easeagent report ${project.version} com.megaease.easeagent httpserver ${project.version} com.megaease.easeagent log4j2-api org.slf4j slf4j-api org.apache.commons commons-lang3 commons-codec commons-codec com.fasterxml.jackson.core jackson-core com.fasterxml.jackson.core jackson-databind com.j256.simplejmx simplejmx 1.19 test javax.servlet javax.servlet-api provided io.projectreactor reactor-core provided net.bytebuddy byte-buddy-agent test com.megaease.easeagent log4j2-mock ${project.version} test src/main/resources true org.codehaus.mojo cobertura-maven-plugin html **/AdviceTo* **/Configurable* ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/AppendBootstrapClassLoaderSearch.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core; import com.google.common.base.Function; import com.google.common.collect.Maps; import com.google.common.io.CharStreams; import com.google.common.io.Closeables; import com.megaease.easeagent.plugin.AppendBootstrapLoader; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.ClassFileLocator; import net.bytebuddy.dynamic.loading.ClassInjector; import net.bytebuddy.pool.TypePool; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.instrument.Instrumentation; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Map; import java.util.Set; import static com.google.common.collect.FluentIterable.from; import static com.google.common.collect.Maps.uniqueIndex; import static java.util.Collections.list; public final class AppendBootstrapClassLoaderSearch { private static final File TMP_FILE = new File( AccessController.doPrivileged( new PrivilegedAction() { @Override public String run() { return System.getProperty("java.io.tmpdir"); } }) ); static Set by(Instrumentation inst, ClassInjector.UsingInstrumentation.Target target) throws IOException { final Set names = findClassAnnotatedAutoService(AppendBootstrapLoader.class); ClassInjector.UsingInstrumentation.of(TMP_FILE, target, inst).inject(types(names)); return names; } private static Map types(Set names) { final ClassLoader loader = AppendBootstrapClassLoaderSearch.class.getClassLoader(); final ClassFileLocator locator = ClassFileLocator.ForClassLoader.of(loader); final TypePool pool = TypePool.Default.of(locator); return Maps.transformValues( uniqueIndex(names, input -> pool.describe(input).resolve()), input -> { try { return locator.locate(input).resolve(); } catch (IOException e) { throw new IllegalStateException(e); } }); } private static Set findClassAnnotatedAutoService(Class cls) throws IOException { final ClassLoader loader = AppendBootstrapClassLoaderSearch.class.getClassLoader(); return from(list(loader.getResources("META-INF/services/" + cls.getName()))) .transform(input -> { try { final URLConnection connection = input.openConnection(); final InputStream stream = connection.getInputStream(); return new InputStreamReader(stream, StandardCharsets.UTF_8); } catch (IOException e) { throw new IllegalStateException(e); } }) .transformAndConcat((Function>) input -> { try { return CharStreams.readLines(input); } catch (IOException e) { throw new IllegalStateException(e); } finally { Closeables.closeQuietly(input); } }) .toSet(); } private AppendBootstrapClassLoaderSearch() { } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/Bootstrap.java ================================================ /* * Copyright (c) 2017, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core; import com.megaease.easeagent.config.*; import com.megaease.easeagent.context.ContextManager; import com.megaease.easeagent.core.config.*; import com.megaease.easeagent.core.info.AgentInfoFactory; import com.megaease.easeagent.core.plugin.BaseLoader; import com.megaease.easeagent.core.plugin.BridgeDispatcher; import com.megaease.easeagent.core.plugin.PluginLoader; import com.megaease.easeagent.httpserver.nano.AgentHttpHandlerProvider; import com.megaease.easeagent.httpserver.nano.AgentHttpServer; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.api.config.ConfigConst; import com.megaease.easeagent.plugin.api.metric.MetricProvider; import com.megaease.easeagent.plugin.api.middleware.RedirectProcessor; import com.megaease.easeagent.plugin.api.trace.TracingProvider; import com.megaease.easeagent.plugin.bean.AgentInitializingBean; import com.megaease.easeagent.plugin.bean.BeanProvider; import com.megaease.easeagent.plugin.bridge.AgentInfo; import com.megaease.easeagent.plugin.bridge.EaseAgent; import com.megaease.easeagent.plugin.report.AgentReport; import com.megaease.easeagent.plugin.utils.common.StringUtils; import com.megaease.easeagent.report.AgentReportAware; import com.megaease.easeagent.report.DefaultAgentReport; import lombok.SneakyThrows; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.ClassFileLocator; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.loading.ClassInjector; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.utility.JavaModule; import javax.management.MBeanServer; import javax.management.ObjectName; import java.lang.instrument.Instrumentation; import java.lang.management.ManagementFactory; import java.net.URLClassLoader; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import static net.bytebuddy.matcher.ElementMatchers.*; @SuppressWarnings("unused") public class Bootstrap { private static final Logger LOGGER = LoggerFactory.getLogger(Bootstrap.class); private static final String AGENT_SERVER_PORT_KEY = ConfigFactory.AGENT_SERVER_PORT; private static final String AGENT_SERVER_ENABLED_KEY = ConfigFactory.AGENT_SERVER_ENABLED; private static final String AGENT_MIDDLEWARE_UPDATE = "easeagent.middleware.update"; private static final int DEF_AGENT_SERVER_PORT = 9900; static final String MX_BEAN_OBJECT_NAME = "com.megaease.easeagent:type=ConfigManager"; private static ContextManager contextManager; private Bootstrap() { } @SneakyThrows public static void start(String args, Instrumentation inst, String javaAgentJarPath) { long begin = System.nanoTime(); System.setProperty(ConfigConst.AGENT_JAR_PATH, javaAgentJarPath); // add bootstrap classes Set bootstrapClassSet = AppendBootstrapClassLoaderSearch.by(inst, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Injected class: {}", bootstrapClassSet); } // initiate configuration String configPath = ConfigFactory.getConfigPath(); if (StringUtils.isEmpty(configPath)) { configPath = args; } ClassLoader classLoader = Bootstrap.class.getClassLoader(); final AgentInfo agentInfo = AgentInfoFactory.loadAgentInfo(classLoader); EaseAgent.agentInfo = agentInfo; final GlobalConfigs conf = ConfigFactory.loadConfigs(configPath, classLoader); wrapConfig(conf); // loader check GlobalAgentHolder.setAgentClassLoader((URLClassLoader) Bootstrap.class.getClassLoader()); EaseAgent.agentClassloader = GlobalAgentHolder::getAgentClassLoader; // init Context/API contextManager = ContextManager.build(conf); EaseAgent.dispatcher = new BridgeDispatcher(); // initInnerHttpServer initHttpServer(conf); // redirection RedirectProcessor.INSTANCE.init(); // reporter final AgentReport agentReport = DefaultAgentReport.create(conf); GlobalAgentHolder.setAgentReport(agentReport); EaseAgent.agentReport = agentReport; // load plugins AgentBuilder builder = getAgentBuilder(conf, false); builder = PluginLoader.load(builder, conf); // provider & beans loadProvider(conf, agentReport); long installBegin = System.currentTimeMillis(); builder.installOn(inst); LOGGER.info("installBegin use time: {}ms", (System.currentTimeMillis() - installBegin)); LOGGER.info("Initialization has took {}ns", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - begin)); } private static void initHttpServer(Configs conf) { // inner httpserver Integer port = conf.getInt(AGENT_SERVER_PORT_KEY); if (port == null) { port = DEF_AGENT_SERVER_PORT; } String portStr = System.getProperty(AGENT_SERVER_PORT_KEY, String.valueOf(port)); port = Integer.parseInt(portStr); AgentHttpServer agentHttpServer = new AgentHttpServer(port); boolean httpServerEnabled = conf.getBoolean(AGENT_SERVER_ENABLED_KEY); if (httpServerEnabled) { agentHttpServer.startServer(); LOGGER.info("start agent http server on port:{}", port); } GlobalAgentHolder.setAgentHttpServer(agentHttpServer); // add httpHandler agentHttpServer.addHttpRoute(new ServiceUpdateAgentHttpHandler()); agentHttpServer.addHttpRoute(new CanaryUpdateAgentHttpHandler()); agentHttpServer.addHttpRoute(new CanaryListUpdateAgentHttpHandler()); agentHttpServer.addHttpRoute(new PluginPropertyHttpHandler()); agentHttpServer.addHttpRoute(new PluginPropertiesHttpHandler()); } private static void loadProvider(final Configs conf, final AgentReport agentReport) { List providers = BaseLoader.loadOrdered(BeanProvider.class); providers.forEach(input -> provider(input, conf, agentReport)); } private static void provider(final BeanProvider beanProvider, final Configs conf, final AgentReport agentReport) { if (beanProvider instanceof ConfigAware) { ((ConfigAware) beanProvider).setConfig(conf); } if (beanProvider instanceof AgentReportAware) { ((AgentReportAware) beanProvider).setAgentReport(agentReport); } if (beanProvider instanceof AgentHttpHandlerProvider) { GlobalAgentHolder.getAgentHttpServer() .addHttpRoutes(((AgentHttpHandlerProvider) beanProvider).getAgentHttpHandlers()); } if (beanProvider instanceof AgentInitializingBean) { ((AgentInitializingBean) beanProvider).afterPropertiesSet(); } if (beanProvider instanceof TracingProvider) { TracingProvider tracingProvider = (TracingProvider) beanProvider; contextManager.setTracing(tracingProvider); } if (beanProvider instanceof MetricProvider) { contextManager.setMetric((MetricProvider) beanProvider); } } public static AgentBuilder getAgentBuilder(Configs config, boolean test) { // config may use to add some classes to be ignored in future long buildBegin = System.currentTimeMillis(); AgentBuilder builder = new AgentBuilder.Default() .with(LISTENER) .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) .with(AgentBuilder.TypeStrategy.Default.REDEFINE) .with(AgentBuilder.LocationStrategy.ForClassLoader.STRONG .withFallbackTo(ClassFileLocator.ForClassLoader.ofSystemLoader())); AgentBuilder.Ignored ignore = builder.ignore(isSynthetic()) .or(nameStartsWith("sun.").and(not(nameStartsWith("sun.net.www.protocol.http")) )) .or(nameStartsWith("com.sun.")) .or(nameStartsWith("brave.")) .or(nameStartsWith("zipkin2.")) .or(nameStartsWith("com.fasterxml")) .or(nameStartsWith("org.apache.logging") .and(not(hasSuperClass(named("org.apache.logging.log4j.spi.AbstractLogger"))))) .or(nameStartsWith("kotlin.")) .or(nameStartsWith("javax.")) .or(nameStartsWith("net.bytebuddy.")) .or(nameStartsWith("com\\.sun\\.proxy\\.\\$Proxy.+")) .or(nameStartsWith("java\\.lang\\.invoke\\.BoundMethodHandle\\$Species_L.+")) .or(nameStartsWith("org.junit.")) .or(nameStartsWith("junit.")) .or(nameStartsWith("com.intellij.")); // config used here to avoid warning of unused if (!test && config != null) { builder = ignore .or(nameStartsWith("com.megaease.easeagent.")); } else { builder = ignore; } LOGGER.info("AgentBuilder use time: {}", (System.currentTimeMillis() - buildBegin)); return builder; } @SneakyThrows static void registerMBeans(ConfigManagerMXBean conf) { long begin = System.currentTimeMillis(); MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); ObjectName mxBeanName = new ObjectName(MX_BEAN_OBJECT_NAME); ClassLoader customClassLoader = Thread.currentThread().getContextClassLoader(); mbs.registerMBean(conf, mxBeanName); LOGGER.info("Register {} as MBean {}, use time: {}", conf.getClass().getName(), mxBeanName, (System.currentTimeMillis() - begin)); } private static ElementMatcher protectedLoaders() { return isBootstrapClassLoader().or(is(Bootstrap.class.getClassLoader())); } private static void wrapConfig(GlobalConfigs configs) { WrappedConfigManager wrappedConfigManager = new WrappedConfigManager(Bootstrap.class.getClassLoader(), configs); registerMBeans(wrappedConfigManager); GlobalAgentHolder.setWrappedConfigManager(wrappedConfigManager); } private static final AgentBuilder.Listener LISTENER = new AgentBuilder.Listener() { @Override public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { // ignored } @Override public void onTransformation(TypeDescription td, ClassLoader ld, JavaModule m, boolean loaded, DynamicType dt) { LOGGER.debug("onTransformation: {} loaded: {} from classLoader {}", td, loaded, ld); } @Override public void onIgnored(TypeDescription td, ClassLoader ld, JavaModule m, boolean loaded) { // ignored } @Override public void onError(String name, ClassLoader ld, JavaModule m, boolean loaded, Throwable error) { LOGGER.debug("Just for Debug-log, transform ends exceptionally, " + "which is sometimes normal and sometimes there is an error: {} error:{} loaded: {} from classLoader {}", name, error, loaded, ld); } @Override public void onComplete(String name, ClassLoader ld, JavaModule m, boolean loaded) { // ignored } }; } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/GlobalAgentHolder.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.core; import com.megaease.easeagent.config.WrappedConfigManager; import com.megaease.easeagent.httpserver.nano.AgentHttpServer; import com.megaease.easeagent.plugin.report.AgentReport; import java.net.URLClassLoader; public class GlobalAgentHolder { private static WrappedConfigManager wrappedConfigManager; private static AgentHttpServer agentHttpServer; private static AgentReport agentReport; private static URLClassLoader agentLoader; private GlobalAgentHolder() {} public static void setWrappedConfigManager(WrappedConfigManager config) { wrappedConfigManager = config; } public static WrappedConfigManager getWrappedConfigManager() { return wrappedConfigManager; } public static void setAgentHttpServer(AgentHttpServer server) { agentHttpServer = server; } public static AgentHttpServer getAgentHttpServer() { return agentHttpServer; } public static void setAgentReport(AgentReport report) { agentReport = report; } public static AgentReport getAgentReport() { return agentReport; } public static void setAgentClassLoader(URLClassLoader loader) { agentLoader = loader; } public static URLClassLoader getAgentClassLoader() { return agentLoader; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/config/CanaryListUpdateAgentHttpHandler.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.core.config; import com.megaease.easeagent.core.GlobalAgentHolder; import com.megaease.easeagent.httpserver.nano.AgentHttpServer; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; public class CanaryListUpdateAgentHttpHandler extends ConfigsUpdateAgentHttpHandler { private static final Logger LOGGER = LoggerFactory.getLogger(CanaryListUpdateAgentHttpHandler.class); public static final AtomicInteger LAST_COUNT = new AtomicInteger(0); public CanaryListUpdateAgentHttpHandler() { this.mxBeanConfig = GlobalAgentHolder.getWrappedConfigManager(); } @Override public String getPath() { return "/config-global-transmission"; } @Override @SuppressWarnings("unchecked") public Response processJsonConfig(Map map, Map urlParams) { LOGGER.info("call /config-global-transmission. configs: {}", map); synchronized (LAST_COUNT) { List headers = (List) map.get("headers"); Map config = new HashMap<>(); for (int i = 0; i < headers.size(); i++) { config.put("easeagent.progress.forwarded.headers.global.transmission." + i, headers.get(i)); } int last = LAST_COUNT.get(); if (headers.size() < last) { for (int i = headers.size(); i < last; i++) { config.put("easeagent.progress.forwarded.headers.global.transmission." + i, ""); } } LAST_COUNT.set(headers.size()); this.mxBeanConfig.updateConfigs(config); } return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, (String) null); } @Override public Response processConfig(Map config, Map urlParams, String version) { this.mxBeanConfig.updateCanary2(config, version); return null; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/config/CanaryUpdateAgentHttpHandler.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.core.config; import com.megaease.easeagent.core.GlobalAgentHolder; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response; import java.util.Map; public class CanaryUpdateAgentHttpHandler extends ConfigsUpdateAgentHttpHandler { public CanaryUpdateAgentHttpHandler() { this.mxBeanConfig = GlobalAgentHolder.getWrappedConfigManager(); } @Override public String getPath() { return "/config-canary"; } @Override public Response processConfig(Map config, Map urlParams, String version) { this.mxBeanConfig.updateCanary2(config, version); return null; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/config/ConfigsUpdateAgentHttpHandler.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.core.config; import com.megaease.easeagent.config.ConfigManagerMXBean; import com.megaease.easeagent.httpserver.nano.AgentHttpHandler; import com.megaease.easeagent.httpserver.nano.AgentHttpServer; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.IHTTPSession; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status; import com.megaease.easeagent.httpserver.nanohttpd.router.RouterNanoHTTPD; import com.megaease.easeagent.plugin.utils.common.JsonUtil; import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import java.util.HashMap; import java.util.Map; public abstract class ConfigsUpdateAgentHttpHandler extends AgentHttpHandler { public abstract Response processConfig(Map config, Map urlParams, String version); protected ConfigManagerMXBean mxBeanConfig; static Map toConfigMap(Map map) { Map config = new HashMap<>(Math.max(map.size(), 8)); map.forEach((s, o) -> config.put(s, o.toString())); return config; } @SneakyThrows @Override public Response process(RouterNanoHTTPD.UriResource uriResource, Map urlParams, IHTTPSession session) { String body = this.buildRequestBody(session); if (StringUtils.isEmpty(body)) { return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, (String) null); } Map map = JsonUtil.toMap(body); if (map == null) { return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, (String) null); } return processJsonConfig(map, urlParams); } public Response processJsonConfig(Map map, Map urlParams) { // String version = (String) map.remove("version"); // if (version == null) { // return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, (String) null); // } Map config = toConfigMap(map); Response response = processConfig(config, urlParams, null); if (response != null) { return response; } return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, (String) null); } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/config/PluginPropertiesHttpHandler.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.core.config; import com.megaease.easeagent.config.ConfigUtils; import com.megaease.easeagent.core.GlobalAgentHolder; import com.megaease.easeagent.httpserver.nano.AgentHttpServer; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status; import java.util.HashMap; import java.util.Map; import java.util.Objects; public class PluginPropertiesHttpHandler extends ConfigsUpdateAgentHttpHandler { public PluginPropertiesHttpHandler() { this.mxBeanConfig = GlobalAgentHolder.getWrappedConfigManager(); } @Override public String getPath() { return "/plugins/domains/:domain/namespaces/:namespace/:id/properties"; } @Override public Response processConfig(Map config, Map urlParams, String version) { try { String domain = ConfigUtils.requireNonEmpty(urlParams.get("domain"), "urlParams.domain must not be null and empty."); String namespace = ConfigUtils.requireNonEmpty(urlParams.get("namespace"), "urlParams.namespace must not be null and empty."); String id = ConfigUtils.requireNonEmpty(urlParams.get("id"), "urlParams.id must not be null and empty."); Map changeConfig = new HashMap<>(); for (Map.Entry propertyEntry : config.entrySet()) { String property = ConfigUtils.buildPluginProperty(domain, namespace, id, ConfigUtils.requireNonEmpty(propertyEntry.getKey(), "body.key must not be null and empty.")); String value = Objects.requireNonNull(propertyEntry.getValue(), String.format("body.%s must not be null.", propertyEntry.getKey())); changeConfig.put(property, value); } this.mxBeanConfig.updateService2(changeConfig, version); return null; } catch (Exception e) { return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, e.getMessage()); } } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/config/PluginPropertyHttpHandler.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.core.config; import com.megaease.easeagent.config.ConfigManagerMXBean; import com.megaease.easeagent.config.ConfigUtils; import com.megaease.easeagent.core.GlobalAgentHolder; import com.megaease.easeagent.httpserver.nano.AgentHttpHandler; import com.megaease.easeagent.httpserver.nano.AgentHttpServer; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.IHTTPSession; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status; import com.megaease.easeagent.httpserver.nanohttpd.router.RouterNanoHTTPD; import lombok.SneakyThrows; import java.util.Collections; import java.util.Map; import java.util.Objects; public class PluginPropertyHttpHandler extends AgentHttpHandler { ConfigManagerMXBean mxBeanConfig; public PluginPropertyHttpHandler() { this.mxBeanConfig = GlobalAgentHolder.getWrappedConfigManager(); methods = Collections.singleton( com.megaease.easeagent.httpserver.nanohttpd.protocols.http.request.Method.GET); } @Override public String getPath() { return "/plugins/domains/:domain/namespaces/:namespace/:id/properties/:property/:value/:version"; } @SneakyThrows @Override public Response process(RouterNanoHTTPD.UriResource uriResource, Map urlParams, IHTTPSession session) { String version = urlParams.get("version"); if (version == null) { return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, (String) null); } try { String property = ConfigUtils.buildPluginProperty( ConfigUtils.requireNonEmpty(urlParams.get("domain"), "urlParams.domain must not be null and empty."), ConfigUtils.requireNonEmpty(urlParams.get("namespace"), "urlParams.namespace must not be null and empty."), ConfigUtils.requireNonEmpty(urlParams.get("id"), "urlParams.id must not be null and empty."), ConfigUtils.requireNonEmpty(urlParams.get("property"), "urlParams.property must not be null and empty.")); String value = Objects.requireNonNull(urlParams.get("value"), "urlParams.value must not be null."); this.mxBeanConfig.updateService2(Collections.singletonMap(property, value), version); return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, (String) null); } catch (Exception e) { return Response.newFixedLengthResponse(Status.BAD_REQUEST, AgentHttpServer.JSON_TYPE, e.getMessage()); } } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/config/ServiceUpdateAgentHttpHandler.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.core.config; import com.megaease.easeagent.config.CompatibilityConversion; import com.megaease.easeagent.core.GlobalAgentHolder; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response; import java.util.Map; public class ServiceUpdateAgentHttpHandler extends ConfigsUpdateAgentHttpHandler { public ServiceUpdateAgentHttpHandler() { this.mxBeanConfig = GlobalAgentHolder.getWrappedConfigManager(); } @Override public String getPath() { return "/config"; } @Override public Response processConfig(Map config, Map urlParams, String version) { this.mxBeanConfig.updateService2(CompatibilityConversion.transform(config), version); return null; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/health/HealthProvider.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.health; import com.megaease.easeagent.plugin.api.config.Config; import com.megaease.easeagent.config.ConfigAware; import com.megaease.easeagent.httpserver.nano.AgentHttpHandler; import com.megaease.easeagent.httpserver.nano.AgentHttpHandlerProvider; import com.megaease.easeagent.httpserver.nano.AgentHttpServer; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.IHTTPSession; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.IStatus; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status; import com.megaease.easeagent.httpserver.nanohttpd.router.RouterNanoHTTPD; import com.megaease.easeagent.plugin.bean.BeanProvider; import com.megaease.easeagent.plugin.bean.AgentInitializingBean; import com.megaease.easeagent.plugin.api.health.AgentHealth; import java.util.ArrayList; import java.util.List; import java.util.Map; public class HealthProvider implements AgentHttpHandlerProvider, ConfigAware, AgentInitializingBean, BeanProvider { private static final String EASEAGENT_HEALTH_READINESS_ENABLED = "easeagent.health.readiness.enabled"; private Config config; @Override public List getAgentHttpHandlers() { List list = new ArrayList<>(); list.add(new HealthAgentHttpHandler()); list.add(new LivenessAgentHttpHandler()); list.add(new ReadinessAgentHttpHandler()); return list; } @Override public void setConfig(Config config) { this.config = config; } @Override public void afterPropertiesSet() { AgentHealth.setReadinessEnabled(this.config.getBoolean(EASEAGENT_HEALTH_READINESS_ENABLED)); } public static class HealthAgentHttpHandler extends AgentHttpHandler { @Override public String getPath() { return "/health"; } @Override public Response process(RouterNanoHTTPD.UriResource uriResource, Map urlParams, IHTTPSession session) { return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, (String) null); } } public static class LivenessAgentHttpHandler extends HealthAgentHttpHandler { @Override public String getPath() { return "/health/liveness"; } } public static class ReadinessAgentHttpHandler extends HealthAgentHttpHandler { @Override public String getPath() { return "/health/readiness"; } @Override public Response process(RouterNanoHTTPD.UriResource uriResource, Map urlParams, IHTTPSession session) { if (AgentHealth.INSTANCE.isReadinessEnabled()) { if (AgentHealth.INSTANCE.isReady()) { return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, (String) null); } return Response.newFixedLengthResponse(HStatus.SERVICE_UNAVAILABLE, AgentHttpServer.JSON_TYPE, (String) null); } return super.process(uriResource, urlParams, session); } } enum HStatus implements IStatus { /** * service unavailable */ SERVICE_UNAVAILABLE(503, "Service Unavailable"), ; private final int requestStatus; private final String description; HStatus(int requestStatus, String description) { this.requestStatus = requestStatus; this.description = description; } @Override public String getDescription() { return description; } @Override public int getRequestStatus() { return requestStatus; } } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/info/AgentInfoFactory.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.info; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.bridge.AgentInfo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class AgentInfoFactory { private static final Logger LOGGER = LoggerFactory.getLogger(AgentInfoFactory.class); public static final String AGENT_TYPE = "EaseAgent"; private static final String VERSION_FILE = "version.txt"; public static AgentInfo loadAgentInfo(ClassLoader classLoader) { return new AgentInfo(AGENT_TYPE, loadVersion(classLoader, VERSION_FILE)); } private static String loadVersion(ClassLoader classLoader, String file) { try (InputStream in = classLoader.getResourceAsStream(file)) { BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String version = reader.readLine(); reader.close(); return version; } catch (IOException e) { LOGGER.warn("Load config file:{} by classloader:{} failure: {}", file, classLoader.toString(), e); } return ""; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/info/AgentInfoProvider.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.info; import com.megaease.easeagent.core.utils.JsonUtil; import com.megaease.easeagent.httpserver.nano.AgentHttpHandler; import com.megaease.easeagent.httpserver.nano.AgentHttpHandlerProvider; import com.megaease.easeagent.httpserver.nano.AgentHttpServer; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.IHTTPSession; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Response; import com.megaease.easeagent.httpserver.nanohttpd.protocols.http.response.Status; import com.megaease.easeagent.httpserver.nanohttpd.router.RouterNanoHTTPD; import com.megaease.easeagent.plugin.bean.BeanProvider; import com.megaease.easeagent.plugin.bridge.EaseAgent; import java.util.ArrayList; import java.util.List; import java.util.Map; public class AgentInfoProvider implements AgentHttpHandlerProvider, BeanProvider { @Override public List getAgentHttpHandlers() { List list = new ArrayList<>(); list.add(new AgentInfoHttpHandler()); return list; } public static class AgentInfoHttpHandler extends AgentHttpHandler { @Override public String getPath() { return "/agent-info"; } @Override public Response process(RouterNanoHTTPD.UriResource uriResource, Map urlParams, IHTTPSession session) { return Response.newFixedLengthResponse(Status.OK, AgentHttpServer.JSON_TYPE, JsonUtil.toJson(EaseAgent.getAgentInfo())); } } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/BaseLoader.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin; import com.megaease.easeagent.plugin.Ordered; import com.megaease.easeagent.plugin.api.logging.Logger; import com.megaease.easeagent.plugin.bridge.EaseAgent; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; public class BaseLoader { private static final Logger logger = EaseAgent.loggerFactory.getLogger(BaseLoader.class); public static List load(Class serviceClass) { List result = new ArrayList<>(); java.util.ServiceLoader services = ServiceLoader.load(serviceClass); for (Iterator it = services.iterator(); it.hasNext(); ) { try { result.add(it.next()); } catch (UnsupportedClassVersionError e) { logger.info("Unable to load class: {}", e.getMessage()); logger.info("Please check the plugin compile Java version configuration," + " and it should not latter than current JVM runtime"); } } return result; } public static List loadOrdered(Class serviceClass) { List result = load(serviceClass); result.sort(Comparator.comparing(Ordered::order)); return result; } private BaseLoader() {} } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/BridgeDispatcher.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin; import com.megaease.easeagent.plugin.interceptor.MethodInfo; import com.megaease.easeagent.plugin.api.Context; import com.megaease.easeagent.plugin.api.InitializeContext; import com.megaease.easeagent.plugin.api.dispatcher.IDispatcher; import com.megaease.easeagent.plugin.bridge.EaseAgent; public class BridgeDispatcher implements IDispatcher { @Override public void enter(int chainIndex, MethodInfo info) { InitializeContext context = EaseAgent.initializeContextSupplier .getContext(); if (context.isNoop()) { return; } Dispatcher.enter(chainIndex, info, context); } @Override public Object exit(int chainIndex, MethodInfo methodInfo, Context context, Object result, Throwable e) { if (context.isNoop() || !(context instanceof InitializeContext)) { return result; } InitializeContext iContext = (InitializeContext)context; methodInfo.throwable(e); methodInfo.retValue(result); Dispatcher.exit(chainIndex, methodInfo, iContext); if (methodInfo.isChanged()) { result = methodInfo.getRetValue(); } return result; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/CommonInlineAdvice.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin; import com.megaease.easeagent.core.plugin.annotation.Index; import com.megaease.easeagent.core.plugin.transformer.advice.AgentAdvice.NoExceptionHandler; import com.megaease.easeagent.plugin.interceptor.MethodInfo; import com.megaease.easeagent.plugin.api.InitializeContext; import com.megaease.easeagent.plugin.bridge.EaseAgent; import net.bytebuddy.asm.Advice; import net.bytebuddy.implementation.bytecode.assign.Assigner; /** * uniform interceptor entrance * get interceptor chain thought index generated when transform */ // suppress all warnings for the code at these warnings is intentionally written this way @SuppressWarnings("all") public class CommonInlineAdvice { private static final String CONTEXT = "easeagent_context"; private static final String POS = "easeagent_pos"; @Advice.OnMethodEnter(suppress = NoExceptionHandler.class) public static MethodInfo enter(@Index int index, @Advice.This(optional = true) Object invoker, @Advice.Origin("#t") String type, @Advice.Origin("#m") String method, @Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] args, @Advice.Local(CONTEXT) InitializeContext context) { context = EaseAgent.initializeContextSupplier.getContext(); if (context.isNoop()) { return null; } MethodInfo methodInfo = MethodInfo.builder() .invoker(invoker) .type(type) .method(method) .args(args) .build(); Dispatcher.enter(index, methodInfo, context); if (methodInfo.isChanged()) { args = methodInfo.getArgs(); } return methodInfo; } @Advice.OnMethodExit(onThrowable = Exception.class, suppress = NoExceptionHandler.class) // @Advice.OnMethodExit(suppress = NoExceptionHandler.class) public static void exit(@Index int index, @Advice.Enter MethodInfo methodInfo, @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result, @Advice.Thrown(readOnly = false, typing = Assigner.Typing.DYNAMIC) Throwable throwable, @Advice.Local(CONTEXT) InitializeContext context) { if (context.isNoop()) { return; } methodInfo.throwable(throwable); methodInfo.retValue(result); Dispatcher.exit(index, methodInfo, context); if (methodInfo.isChanged()) { result = methodInfo.getRetValue(); } } @Advice.OnMethodExit(suppress = NoExceptionHandler.class) public static void exit(@Index int index, @Advice.This(optional = true) Object invoker, @Advice.Enter MethodInfo methodInfo, @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result, @Advice.Local(CONTEXT) InitializeContext context) { if (context.isNoop()) { return; } methodInfo.setInvoker(invoker); methodInfo.retValue(result); Dispatcher.exit(index, methodInfo, context); if (methodInfo.isChanged()) { result = methodInfo.getRetValue(); } } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/Dispatcher.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin; import com.google.auto.service.AutoService; import com.megaease.easeagent.core.utils.AgentArray; import com.megaease.easeagent.core.utils.ContextUtils; import com.megaease.easeagent.plugin.AppendBootstrapLoader; import com.megaease.easeagent.plugin.interceptor.MethodInfo; import com.megaease.easeagent.plugin.api.InitializeContext; import com.megaease.easeagent.plugin.interceptor.AgentInterceptorChain; @AutoService(AppendBootstrapLoader.class) public final class Dispatcher { private Dispatcher() { } static AgentArray chains = new AgentArray<>(); /** * for chains only modified during related class loading process, * so it doesn't need to consider updating process * otherwise, chain should store in context, avoiding changed during enter and exit */ public static void enter(int index, MethodInfo info, InitializeContext ctx) { AgentInterceptorChain chain = chains.getUncheck(index); int pos = 0; ContextUtils.setBeginTime(ctx); chain.doBefore(info, pos, ctx); } public static Object exit(int index, MethodInfo info, InitializeContext ctx) { AgentInterceptorChain chain = chains.getUncheck(index); int pos = chain.size() - 1; ContextUtils.setEndTime(ctx); return chain.doAfter(info, pos, ctx); } public static AgentInterceptorChain register(int index, AgentInterceptorChain chain) { return chains.putIfAbsent(index, chain); } // for interceptor public static AgentInterceptorChain getChain(int index) { return chains.get(index); } public static boolean updateChain(int index, AgentInterceptorChain chain) { return chains.replace(index, chain) != null; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/PluginLoader.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin; import com.megaease.easeagent.config.ConfigUtils; import com.megaease.easeagent.config.Configs; import com.megaease.easeagent.core.plugin.matcher.ClassTransformation; import com.megaease.easeagent.core.plugin.matcher.MethodTransformation; import com.megaease.easeagent.core.plugin.registry.PluginRegistry; import com.megaease.easeagent.core.plugin.transformer.CompoundPluginTransformer; import com.megaease.easeagent.core.plugin.transformer.DynamicFieldTransformer; import com.megaease.easeagent.core.plugin.transformer.ForAdviceTransformer; import com.megaease.easeagent.core.plugin.transformer.TypeFieldTransformer; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.AgentPlugin; import com.megaease.easeagent.plugin.CodeVersion; import com.megaease.easeagent.plugin.Ordered; import com.megaease.easeagent.plugin.Points; import com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor; import com.megaease.easeagent.plugin.interceptor.InterceptorProvider; import com.megaease.easeagent.plugin.utils.common.StringUtils; import net.bytebuddy.agent.builder.AgentBuilder; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; public class PluginLoader { private PluginLoader() { } static Logger log = LoggerFactory.getLogger(PluginLoader.class); public static AgentBuilder load(AgentBuilder ab, Configs conf) { pluginLoad(); pointsLoad(conf); providerLoad(); Set sortedTransformations = classTransformationLoad(); for (ClassTransformation transformation : sortedTransformations) { ab = ab.type(transformation.getClassMatcher(), transformation.getClassloaderMatcher()) .transform(compound(transformation.isHasDynamicField(), transformation.getMethodTransformations(), transformation.getTypeFieldAccessor())); } return ab; } public static void providerLoad() { for (InterceptorProvider provider : BaseLoader.load(InterceptorProvider.class)) { String pointsClassName = PluginRegistry.getPointsClassName(provider.getAdviceTo()); Points points = PluginRegistry.getPoints(pointsClassName); if (points == null) { log.debug("Unload provider:{}, can not found Points<{}>", provider.getClass().getName(), pointsClassName); continue; } else { log.debug("Loading provider:{}", provider.getClass().getName()); } try { log.debug("provider for:{} at {}", provider.getPluginClassName(), provider.getAdviceTo()); PluginRegistry.register(provider); } catch (Exception | LinkageError e) { log.error( "Unable to load provider in [class {}]", provider.getClass().getName(), e); } } } public static Set classTransformationLoad() { Collection points = PluginRegistry.getPoints(); return points.stream().map(point -> { try { return PluginRegistry.registerClassTransformation(point); } catch (Exception e) { log.error( "Unable to load classTransformation in [class {}]", point.getClass().getName(), e); return null; } }).filter(Objects::nonNull) .sorted(Comparator.comparing(Ordered::order)) .collect(Collectors.toCollection(LinkedHashSet::new)); } public static void pluginLoad() { for (AgentPlugin plugin : BaseLoader.loadOrdered(AgentPlugin.class)) { log.info( "Loading plugin {}:{} [class {}]", plugin.getDomain(), plugin.getNamespace(), plugin.getClass().getName()); try { PluginRegistry.register(plugin); } catch (Exception | LinkageError e) { log.error( "Unable to load extension {}:{} [class {}]", plugin.getDomain(), plugin.getNamespace(), plugin.getClass().getName(), e); } } } public static void pointsLoad(Configs conf) { for (Points points : BaseLoader.load(Points.class)) { if (!isCodeVersion(points, conf)) { continue; } else { log.info("Loading points [class Points<{}>]", points.getClass().getName()); } try { PluginRegistry.register(points); } catch (Exception | LinkageError e) { log.error( "Unable to load extension [class {}]", points.getClass().getName(), e); } } } public static boolean isCodeVersion(Points points, Configs conf) { CodeVersion codeVersion = points.codeVersions(); if (codeVersion.isEmpty()) { return true; } String versionKey = ConfigUtils.buildCodeVersionKey(codeVersion.getKey()); Set versions = new HashSet<>(conf.getStringList(versionKey)); if (versions.isEmpty()) { versions = Points.DEFAULT_VERSIONS; } Set pointVersions = codeVersion.getVersions(); for (String version : versions) { if (pointVersions.contains(version)) { return true; } } log.info("Unload points [class Points<{}>], the config [{}={}] is not in Points.codeVersions()=[{}:{}]", points.getClass().getCanonicalName(), versionKey, String.join(",", versions), codeVersion.getKey(), String.join(",", codeVersion.getVersions())); return false; } /** * @param methodTransformations method matchers under a special classMatcher * @return transform */ public static AgentBuilder.Transformer compound(boolean hasDynamicField, Iterable methodTransformations, String typeFieldAccessor) { List agentTransformers = StreamSupport .stream(methodTransformations.spliterator(), false) .map(ForAdviceTransformer::new) .collect(Collectors.toList()); if (hasDynamicField) { agentTransformers.add(new DynamicFieldTransformer(AgentDynamicFieldAccessor.DYNAMIC_FIELD_NAME)); } if (StringUtils.hasText(typeFieldAccessor)) { agentTransformers.add(new TypeFieldTransformer(typeFieldAccessor)); } return new CompoundPluginTransformer(agentTransformers); } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/annotation/EaseAgentInstrumented.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface EaseAgentInstrumented { int value() default 0; } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/annotation/Index.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface Index { } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/interceptor/InterceptorPluginDecorator.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.interceptor; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.AgentPlugin; import com.megaease.easeagent.plugin.interceptor.Interceptor; import com.megaease.easeagent.plugin.interceptor.MethodInfo; import com.megaease.easeagent.plugin.api.Context; import com.megaease.easeagent.plugin.api.InitializeContext; import com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigImpl; import com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry; import com.megaease.easeagent.plugin.api.config.IPluginConfig; import com.megaease.easeagent.plugin.bridge.NoOpIPluginConfig; import java.util.function.Supplier; public class InterceptorPluginDecorator implements Interceptor { private static final Logger LOGGER = LoggerFactory.getLogger(InterceptorPluginDecorator.class); private final Interceptor interceptor; private final AgentPlugin plugin; private final AutoRefreshPluginConfigImpl config; public InterceptorPluginDecorator(Interceptor interceptor, AgentPlugin plugin) { this.interceptor = interceptor; this.plugin = plugin; this.config = AutoRefreshPluginConfigRegistry.getOrCreate(plugin.getDomain(), plugin.getNamespace(), interceptor.getType()); } public IPluginConfig getConfig() { return this.config.getConfig(); } @Override public void before(MethodInfo methodInfo, Context context) { IPluginConfig cfg = this.config.getConfig(); InitializeContext innerContext = (InitializeContext) context; innerContext.pushConfig(cfg); if (cfg == null || cfg.enabled() || cfg instanceof NoOpIPluginConfig) { innerContext.pushRetBound(); this.interceptor.before(methodInfo, context); } else if (LOGGER.isDebugEnabled()) { LOGGER.debug("plugin.{}.{}.{} is not enabled", config.domain(), config.namespace(), config.id()); } } @Override public void after(MethodInfo methodInfo, Context context) { IPluginConfig cfg = context.getConfig(); InitializeContext innerContext = (InitializeContext) context; try { if (cfg == null || cfg.enabled() || cfg instanceof NoOpIPluginConfig) { try { this.interceptor.after(methodInfo, context); } finally { innerContext.popToBound(); innerContext.popRetBound(); } } } finally { innerContext.popConfig(); } } @Override public String getType() { return this.interceptor.getType(); } @Override public void init(IPluginConfig config, String type, String method, String methodDescriptor) { this.interceptor.init(config, type, method, methodDescriptor); } @Override public void init(IPluginConfig config, int uniqueIndex) { this.interceptor.init(config, uniqueIndex); } @Override public int order() { int pluginOrder = this.plugin.order(); int interceptorOrder = this.interceptor.order(); return (interceptorOrder << 8) + pluginOrder; } public static Supplier getInterceptorSupplier(final AgentPlugin plugin, final Supplier supplier) { return () -> new InterceptorPluginDecorator(supplier.get(), plugin); } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/interceptor/ProviderChain.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.interceptor; import com.megaease.easeagent.plugin.interceptor.InterceptorProvider; import com.megaease.easeagent.plugin.interceptor.Interceptor; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import java.util.stream.Collectors; public class ProviderChain { private final List providers; ProviderChain(List providers) { this.providers = providers; } public List> getSupplierChain() { return this.providers.stream() .map(InterceptorProvider::getInterceptorProvider) .collect(Collectors.toCollection(ArrayList::new)); } public static Builder builder() { return new Builder(); } @SuppressWarnings("all") public static class Builder { private List providers = new ArrayList<>(); Builder() { } public Builder providers(List providers) { this.providers = providers; return this; } public Builder addProvider(InterceptorProvider supplier) { this.providers.add(supplier); return this; } public ProviderChain build() { return new ProviderChain(providers); } @Override public String toString() { return "ProviderChain.Builder(providers=" + this.providers + ")"; } } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/interceptor/ProviderPluginDecorator.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.interceptor; import com.megaease.easeagent.plugin.AgentPlugin; import com.megaease.easeagent.plugin.interceptor.InterceptorProvider; import com.megaease.easeagent.plugin.interceptor.Interceptor; import java.util.function.Supplier; public class ProviderPluginDecorator implements InterceptorProvider { private final AgentPlugin plugin; private final InterceptorProvider provider; public ProviderPluginDecorator(AgentPlugin plugin, InterceptorProvider provider) { this.plugin = plugin; this.provider = provider; } @Override public Supplier getInterceptorProvider() { return () -> { Supplier origin = ProviderPluginDecorator.this.provider.getInterceptorProvider(); return new InterceptorPluginDecorator(origin.get(), this.plugin); }; } @Override public String getAdviceTo() { return this.provider.getAdviceTo(); } @Override public String getPluginClassName() { return this.provider.getPluginClassName(); } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/matcher/ClassLoaderMatcherConvert.java ================================================ /* * Copyright (c) 2022, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.megaease.easeagent.core.plugin.matcher; import com.megaease.easeagent.core.Bootstrap; import com.megaease.easeagent.log4j2.FinalClassloaderSupplier; import com.megaease.easeagent.plugin.matcher.loader.IClassLoaderMatcher; import com.megaease.easeagent.plugin.matcher.loader.NegateClassLoaderMatcher; import net.bytebuddy.matcher.ElementMatcher; import static com.megaease.easeagent.plugin.matcher.loader.ClassLoaderMatcher.*; import static net.bytebuddy.matcher.ElementMatchers.*; public class ClassLoaderMatcherConvert implements Converter> { public static final ClassLoaderMatcherConvert INSTANCE = new ClassLoaderMatcherConvert(); private static final ElementMatcher agentLoaderMatcher = is(Bootstrap.class.getClassLoader()) .or(is(FinalClassloaderSupplier.CLASSLOADER)); @Override public ElementMatcher convert(IClassLoaderMatcher source) { boolean negate; ElementMatcher matcher; if (source instanceof NegateClassLoaderMatcher) { negate = true; source = source.negate(); } else { negate = false; } if (ALL.equals(source)) { matcher = any(); } else { switch (source.getClassLoaderName()) { case BOOTSTRAP_NAME: matcher = isBootstrapClassLoader(); break; case EXTERNAL_NAME: matcher = isExtensionClassLoader(); break; case SYSTEM_NAME: matcher = isSystemClassLoader(); break; case AGENT_NAME: matcher = agentLoaderMatcher; break; default: matcher = new NameMatcher(source.getClassLoaderName()); break; } } if (negate) { return not(matcher); } else { return matcher; } } static class NameMatcher implements ElementMatcher { final String className; public NameMatcher(String name) { this.className = name; } @Override public boolean matches(ClassLoader target) { if (target == null) { return this.className == null; } return this.className.equals(target.getClass().getCanonicalName()); } } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/matcher/ClassMatcherConvert.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.matcher; import com.megaease.easeagent.plugin.asm.Modifier; import com.megaease.easeagent.plugin.matcher.ClassMatcher; import com.megaease.easeagent.plugin.matcher.IClassMatcher; import com.megaease.easeagent.plugin.matcher.operator.AndClassMatcher; import com.megaease.easeagent.plugin.matcher.operator.NegateClassMatcher; import com.megaease.easeagent.plugin.matcher.operator.OrClassMatcher; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher.Junction; import net.bytebuddy.matcher.NegatingMatcher; import static net.bytebuddy.matcher.ElementMatchers.*; public class ClassMatcherConvert implements Converter> { public static final ClassMatcherConvert INSTANCE = new ClassMatcherConvert(); @Override public Junction convert(IClassMatcher source) { if (source == null) { return null; } if (source instanceof AndClassMatcher) { AndClassMatcher andMatcher = (AndClassMatcher) source; Junction leftMatcher = this.convert(andMatcher.getLeft()); Junction rightMatcher = this.convert(andMatcher.getRight()); return leftMatcher.and(rightMatcher); } else if (source instanceof OrClassMatcher) { OrClassMatcher andMatcher = (OrClassMatcher) source; Junction leftMatcher = this.convert(andMatcher.getLeft()); Junction rightMatcher = this.convert(andMatcher.getRight()); return leftMatcher.or(rightMatcher); } else if (source instanceof NegateClassMatcher) { NegateClassMatcher matcher = (NegateClassMatcher) source; Junction notMatcher = this.convert(matcher.getMatcher()); return new NegatingMatcher<>(notMatcher); } if (!(source instanceof ClassMatcher)) { return null; } return this.convert((ClassMatcher) source); } private Junction convert(ClassMatcher matcher) { Junction c; switch (matcher.getMatchType()) { case NAMED: c = named(matcher.getName()); break; case SUPER_CLASS: case INTERFACE: c = hasSuperType(named(matcher.getName())); break; case ANNOTATION: c = isAnnotatedWith(named(matcher.getName())); break; default: return null; } Junction mc = fromModifier(matcher.getModifier(), false); if (mc != null) { c = c.and(mc); } mc = fromModifier(matcher.getNotModifier(), true); if (mc != null) { c = c.and(mc); } // TODO: classloader matcher return c; } Junction fromModifier(int modifier, boolean not) { Junction mc = null; if ((modifier & ClassMatcher.MODIFIER_MASK) != 0) { if ((modifier & Modifier.ACC_ABSTRACT) != 0) { mc = isAbstract(); } if ((modifier & Modifier.ACC_PUBLIC) != 0) { if (mc != null) { mc = not ? mc.or(isPublic()) : mc.and(isPublic()); } else { mc = isPublic(); } } if ((modifier & Modifier.ACC_PRIVATE) != 0) { if (mc != null) { mc = not ? mc.or(isPrivate()) : mc.and(isPrivate()); } else { mc = isPrivate(); } } if ((modifier & Modifier.ACC_INTERFACE) != 0) { if (mc != null) { mc = not ? mc.or(isInterface()) : mc.and(isInterface()); } else { mc = isInterface(); } } if ((modifier & Modifier.ACC_PROTECTED) != 0) { if (mc != null) { mc = not ? mc.or(isProtected()) : mc.and(isProtected()); } else { mc = isProtected(); } } if (not) { mc = new NegatingMatcher<>(mc); } } return mc; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/matcher/ClassTransformation.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.matcher; import com.megaease.easeagent.plugin.Ordered; import lombok.Data; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatcher.Junction; import java.util.Set; import static net.bytebuddy.matcher.ElementMatchers.any; @Data public class ClassTransformation implements Ordered { private int order; private Junction classMatcher; private ElementMatcher classloaderMatcher; private Set methodTransformations; private boolean hasDynamicField; private String typeFieldAccessor; public ClassTransformation(int order, ElementMatcher classloaderMatcher, Junction classMatcher, Set methodTransformations, boolean hasDynamicField, String typeFieldAccessor) { this.order = order; if (classloaderMatcher == null) { this.classloaderMatcher = any(); } else { this.classloaderMatcher = classloaderMatcher; } this.classMatcher = classMatcher; this.methodTransformations = methodTransformations; this.hasDynamicField = hasDynamicField; this.typeFieldAccessor = typeFieldAccessor; } public static Builder builder() { return new Builder(); } @Override public int order() { return this.order; } public static class Builder { private int order; private Junction classMatcher; private ElementMatcher classloaderMatcher = null; private Set methodTransformations; private boolean hasDynamicField; private String typeFieldAccessor; Builder() { } public Builder order(int order) { this.order = order; return this; } public Builder classloaderMatcher(ElementMatcher clmMatcher) { this.classloaderMatcher = clmMatcher; return this; } public Builder classMatcher(Junction classMatcher) { this.classMatcher = classMatcher; return this; } public Builder methodTransformations(Set methodTransformations) { this.methodTransformations = methodTransformations; return this; } public Builder hasDynamicField(boolean hasDynamicField) { this.hasDynamicField = hasDynamicField; return this; } public Builder typeFieldAccessor(String typeFieldAccessor) { this.typeFieldAccessor = typeFieldAccessor; return this; } public ClassTransformation build() { return new ClassTransformation(order, classloaderMatcher, classMatcher, methodTransformations, hasDynamicField, typeFieldAccessor); } public String toString() { return "ClassTransformation.Builder(order=" + this.order + ", classMatcher=" + this.classMatcher + ", methodTransformations=" + this.methodTransformations + ", hasDynamicField=" + this.hasDynamicField + ")"; } } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/matcher/Converter.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.matcher; public interface Converter { T convert(S source); } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/matcher/MethodMatcherConvert.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.matcher; import com.megaease.easeagent.plugin.asm.Modifier; import com.megaease.easeagent.plugin.matcher.ClassMatcher; import com.megaease.easeagent.plugin.matcher.IMethodMatcher; import com.megaease.easeagent.plugin.matcher.MethodMatcher; import com.megaease.easeagent.plugin.matcher.operator.AndMethodMatcher; import com.megaease.easeagent.plugin.matcher.operator.NegateMethodMatcher; import com.megaease.easeagent.plugin.matcher.operator.OrMethodMatcher; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher.Junction; import net.bytebuddy.matcher.NegatingMatcher; import static net.bytebuddy.matcher.ElementMatchers.*; public class MethodMatcherConvert implements Converter> { public static final MethodMatcherConvert INSTANCE = new MethodMatcherConvert(); @Override public Junction convert(IMethodMatcher source) { if (source == null) { return null; } if (source instanceof AndMethodMatcher) { AndMethodMatcher andMatcher = (AndMethodMatcher) source; Junction leftMatcher = this.convert(andMatcher.getLeft()); Junction rightMatcher = this.convert(andMatcher.getRight()); return leftMatcher.and(rightMatcher); } else if (source instanceof OrMethodMatcher) { OrMethodMatcher andMatcher = (OrMethodMatcher) source; Junction leftMatcher = this.convert(andMatcher.getLeft()); Junction rightMatcher = this.convert(andMatcher.getRight()); return leftMatcher.or(rightMatcher); } else if (source instanceof NegateMethodMatcher) { NegateMethodMatcher matcher = (NegateMethodMatcher) source; Junction notMatcher = this.convert(matcher.getMatcher()); return new NegatingMatcher<>(notMatcher); } if (!(source instanceof MethodMatcher)) { return null; } return this.convert((MethodMatcher) source); } private Junction convert(MethodMatcher matcher) { Junction c = null; if (matcher.getName() != null && matcher.getNameMatchType() != null) { switch (matcher.getNameMatchType()) { case EQUALS: if ("".equals(matcher.getName())) { c = isConstructor(); } else { c = named(matcher.getName()); } break; case START_WITH: c = nameStartsWith(matcher.getName()); break; case END_WITH: c = nameEndsWith(matcher.getName()); break; case CONTAINS: c = nameContains(matcher.getName()); break; default: return null; } } Junction mc = fromModifier(matcher.getModifier(), false); if (mc != null) { c = c == null ? mc : c.and(mc); } mc = fromModifier(matcher.getNotModifier(), true); if (mc != null) { c = c == null ? mc : c.and(mc); } if (matcher.getReturnType() != null) { mc = returns(named(matcher.getReturnType())); c = c == null ? mc : c.and(mc); } if (matcher.getArgsLength() > -1) { mc = takesArguments(matcher.getArgsLength()); c = c == null ? mc : c.and(mc); } String[] args = matcher.getArgs(); if (args != null) { for (int i = 0; i < args.length; i++) { if (args[i] != null) { mc = takesArgument(i, named(args[i])); c = c == null ? mc : c.and(mc); } } } if (matcher.getArgsLength() >= 0) { mc = takesArguments(matcher.getArgsLength()); c = c == null ? mc : c.and(mc); } if (matcher.getOverriddenFrom() != null) { Junction cls = ClassMatcherConvert.INSTANCE.convert(matcher.getOverriddenFrom()); mc = isOverriddenFrom(cls); c = c == null ? mc : c.and(mc); } return c; } Junction fromModifier(int modifier, boolean not) { Junction mc = null; if ((modifier & ClassMatcher.MODIFIER_MASK) != 0) { if ((modifier & Modifier.ACC_ABSTRACT) != 0) { mc = isAbstract(); } if ((modifier & Modifier.ACC_PUBLIC) != 0) { if (mc != null) { mc = not ? mc.or(isPublic()) : mc.and(isPublic()); } else { mc = isPublic(); } } if ((modifier & Modifier.ACC_PRIVATE) != 0) { if (mc != null) { mc = not ? mc.or(isPrivate()) : mc.and(isPrivate()); } else { mc = isPrivate(); } } if ((modifier & Modifier.ACC_PROTECTED) != 0) { if (mc != null) { mc = not ? mc.or(isProtected()) : mc.and(isProtected()); } else { mc = isProtected(); } } if ((modifier & Modifier.ACC_STATIC) != 0) { if (mc != null) { mc = not ? mc.or(isStatic()) : mc.and(isStatic()); } else { mc = isStatic(); } } if (not) { mc = new NegatingMatcher<>(mc); } } return mc; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/matcher/MethodTransformation.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.matcher; import com.megaease.easeagent.core.plugin.Dispatcher; import com.megaease.easeagent.core.plugin.interceptor.InterceptorPluginDecorator; import com.megaease.easeagent.core.plugin.interceptor.ProviderChain; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.api.config.ConfigConst; import com.megaease.easeagent.plugin.enums.Order; import com.megaease.easeagent.plugin.interceptor.Interceptor; import com.megaease.easeagent.plugin.Ordered; import com.megaease.easeagent.plugin.interceptor.AgentInterceptorChain; import lombok.Data; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher.Junction; import java.util.Comparator; import java.util.List; import java.util.function.Supplier; import java.util.stream.Collectors; @Data @SuppressWarnings("unused") public class MethodTransformation { private static final Logger log = LoggerFactory.getLogger(MethodTransformation.class); private int index; private Junction matcher; private ProviderChain.Builder providerBuilder; public MethodTransformation(int index, Junction matcher, ProviderChain.Builder chain) { this.index = index; this.matcher = matcher; this.providerBuilder = chain; } public AgentInterceptorChain getAgentInterceptorChain(final int uniqueIndex, final String type, final String method, final String methodDescription) { List> suppliers = this.providerBuilder.build() .getSupplierChain(); List interceptors = suppliers.stream() .map(Supplier::get) .sorted(Comparator.comparing(Ordered::order)) .collect(Collectors.toList()); interceptors.forEach(i -> { InterceptorPluginDecorator interceptor; if (i instanceof InterceptorPluginDecorator) { interceptor = (InterceptorPluginDecorator) i; try { interceptor.init(interceptor.getConfig(), type, method, methodDescription); interceptor.init(interceptor.getConfig(), uniqueIndex); } catch (Exception e) { log.error("Interceptor init fail: {}::{}, {}", type, method, interceptor.getClass().getSimpleName()); } } }); return new AgentInterceptorChain(interceptors); } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/registry/AdviceRegistry.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.registry; import com.megaease.easeagent.core.plugin.matcher.MethodTransformation; import com.megaease.easeagent.core.plugin.transformer.advice.AgentAdvice.Dispatcher; import com.megaease.easeagent.core.plugin.transformer.advice.AgentAdvice.OffsetMapping; import com.megaease.easeagent.core.plugin.transformer.advice.AgentJavaConstantValue; import com.megaease.easeagent.core.plugin.transformer.advice.MethodIdentityJavaConstant; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.interceptor.AgentInterceptorChain; import com.megaease.easeagent.plugin.utils.common.WeakConcurrentMap; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.implementation.bytecode.StackManipulation; import java.lang.ref.WeakReference; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; public class AdviceRegistry { private AdviceRegistry() { } private static final ThreadLocal> CURRENT_CLASS_LOADER = new ThreadLocal<>(); private static final Logger log = LoggerFactory.getLogger(AdviceRegistry.class); static Map methodsSet = new ConcurrentHashMap<>(); public static Integer check(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit) { String clazz = instrumentedType.getName(); String method = instrumentedMethod.getName(); String methodDescriptor = instrumentedMethod.getDescriptor(); String key = clazz + ":" + method + methodDescriptor; PointcutsUniqueId newIdentity = new PointcutsUniqueId(); PointcutsUniqueId pointcutsUniqueId = methodsSet.putIfAbsent(key, newIdentity); Integer pointcutIndex; boolean merge = false; // already exist if (pointcutsUniqueId != null) { newIdentity.tryRelease(); pointcutIndex = getPointcutIndex(methodEnter); // this pointcut's interceptors have injected into chain if (pointcutsUniqueId.checkPointcutExist(pointcutIndex)) { if (pointcutsUniqueId.checkClassloaderExist()) { // don't need to instrumented again. return 0; } else { /* * Although the interceptor of the pointcut has been injected, * the method of this class owned by current loader has not been instrumented */ updateStackManipulation(methodEnter, pointcutsUniqueId.getUniqueId()); updateStackManipulation(methodExit, pointcutsUniqueId.getUniqueId()); return pointcutsUniqueId.getUniqueId(); } } else { // Orchestration merge = true; } } else { // new pointcutsUniqueId = newIdentity; pointcutIndex = updateStackManipulation(methodEnter, pointcutsUniqueId.getUniqueId()); updateStackManipulation(methodExit, pointcutsUniqueId.getUniqueId()); } // merge or registry MethodTransformation methodTransformation = PluginRegistry.getMethodTransformation(pointcutIndex); if (methodTransformation == null) { log.error("MethodTransformation get fail for {}", pointcutIndex); return 0; } int uniqueId = pointcutsUniqueId.getUniqueId(); AgentInterceptorChain chain = methodTransformation .getAgentInterceptorChain(uniqueId, clazz, method, methodDescriptor); try { pointcutsUniqueId.lock(); AgentInterceptorChain previousChain = com.megaease.easeagent.core.plugin.Dispatcher.getChain(uniqueId); if (previousChain == null) { com.megaease.easeagent.core.plugin.Dispatcher.register(uniqueId, chain); } else { chain.merge(previousChain); com.megaease.easeagent.core.plugin.Dispatcher.updateChain(uniqueId, chain); } } finally { pointcutsUniqueId.unlock(); } if (merge) { return 0; } return uniqueId; } static Integer getPointcutIndex(Dispatcher.Resolved resolved) { int index = 0; Map enterMap = resolved.getOffsetMapping(); for (Map.Entry offset : enterMap.entrySet()) { OffsetMapping om = offset.getValue(); if (!(om instanceof OffsetMapping.ForStackManipulation)) { continue; } OffsetMapping.ForStackManipulation forStackManipulation = (OffsetMapping.ForStackManipulation) om; if (!(forStackManipulation.getStackManipulation() instanceof AgentJavaConstantValue)) { continue; } AgentJavaConstantValue value = (AgentJavaConstantValue) forStackManipulation.getStackManipulation(); index = value.getPointcutIndex(); break; } return index; } static Integer updateStackManipulation(Dispatcher.Resolved resolved, Integer value) { int index = 0; Map enterMap = resolved.getOffsetMapping(); for (Map.Entry offset : enterMap.entrySet()) { OffsetMapping om = offset.getValue(); if (!(om instanceof OffsetMapping.ForStackManipulation)) { continue; } OffsetMapping.ForStackManipulation forStackManipulation = (OffsetMapping.ForStackManipulation) om; if (!(forStackManipulation.getStackManipulation() instanceof AgentJavaConstantValue)) { continue; } AgentJavaConstantValue oldValue = (AgentJavaConstantValue) forStackManipulation.getStackManipulation(); index = oldValue.getPointcutIndex(); MethodIdentityJavaConstant constant = new MethodIdentityJavaConstant(value); StackManipulation stackManipulation = new AgentJavaConstantValue(constant, index); enterMap.put(offset.getKey(), forStackManipulation.with(stackManipulation)); return index; } return index; } public static void setCurrentClassLoader(ClassLoader loader) { CURRENT_CLASS_LOADER.set(new WeakReference<>(loader)); } public static ClassLoader getCurrentClassLoader() { return CURRENT_CLASS_LOADER.get().get(); } public static void cleanCurrentClassLoader() { CURRENT_CLASS_LOADER.remove(); } private static class PointcutsUniqueId { static AtomicInteger index = new AtomicInteger(1); ReentrantLock lock = new ReentrantLock(); int uniqueId; ConcurrentHashMap pointcutIndexSet = new ConcurrentHashMap<>(); WeakConcurrentMap cache = new WeakConcurrentMap<>(); public PointcutsUniqueId() { this.uniqueId = index.incrementAndGet(); } public boolean checkPointcutExist(Integer pointcutIndex) { return this.pointcutIndexSet.putIfAbsent(pointcutIndex, pointcutIndex) != null; } public int getUniqueId() { return this.uniqueId; } public boolean checkClassloaderExist() { ClassLoader loader = getCurrentClassLoader(); return cache.putIfProbablyAbsent(loader, true) != null; } public void lock() { this.lock.lock(); } public void unlock() { this.lock.unlock(); } /** * Some empty slots may appear, the effect can be ignored and can be optimized later */ public void tryRelease() { int id = this.uniqueId; index.compareAndSet(id, id - 1); } } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/registry/PluginRegistry.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.registry; import com.google.common.base.Strings; import com.megaease.easeagent.core.plugin.interceptor.ProviderChain; import com.megaease.easeagent.core.plugin.interceptor.ProviderChain.Builder; import com.megaease.easeagent.core.plugin.interceptor.ProviderPluginDecorator; import com.megaease.easeagent.core.plugin.matcher.*; import com.megaease.easeagent.core.utils.AgentArray; import com.megaease.easeagent.plugin.AgentPlugin; import com.megaease.easeagent.plugin.Points; import com.megaease.easeagent.plugin.api.logging.Logger; import com.megaease.easeagent.plugin.bridge.EaseAgent; import com.megaease.easeagent.plugin.interceptor.InterceptorProvider; import com.megaease.easeagent.plugin.matcher.IClassMatcher; import com.megaease.easeagent.plugin.matcher.IMethodMatcher; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatcher.Junction; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; public class PluginRegistry { static Logger log = EaseAgent.getLogger(PluginRegistry.class); static final ConcurrentHashMap QUALIFIER_TO_PLUGIN = new ConcurrentHashMap<>(); static final ConcurrentHashMap POINTS_TO_PLUGIN = new ConcurrentHashMap<>(); static final ConcurrentHashMap PLUGIN_CLASSNAME_TO_PLUGIN = new ConcurrentHashMap<>(); static final ConcurrentHashMap POINTS_CLASSNAME_TO_POINTS = new ConcurrentHashMap<>(); static final ConcurrentHashMap QUALIFIER_TO_INDEX = new ConcurrentHashMap<>(); static final ConcurrentHashMap INDEX_TO_METHOD_TRANSFORMATION = new ConcurrentHashMap<>(); static final AgentArray INTERCEPTOR_PROVIDERS = new AgentArray<>(); private PluginRegistry() { } public static void register(AgentPlugin plugin) { PLUGIN_CLASSNAME_TO_PLUGIN.putIfAbsent(plugin.getClass().getCanonicalName(), plugin); } public static void register(Points points) { POINTS_CLASSNAME_TO_POINTS.putIfAbsent(points.getClass().getCanonicalName(), points); } public static Collection getPoints() { return POINTS_CLASSNAME_TO_POINTS.values(); } public static Points getPoints(String pointsClassName) { return POINTS_CLASSNAME_TO_POINTS.get(pointsClassName); } private static String getMethodQualifier(String classname, String qualifier) { return classname + ":" + qualifier; } public static ClassTransformation registerClassTransformation(Points points) { String pointsClassName = points.getClass().getCanonicalName(); IClassMatcher classMatcher = points.getClassMatcher(); boolean hasDynamicField = points.isAddDynamicField(); Junction innerClassMatcher = ClassMatcherConvert.INSTANCE.convert(classMatcher); ElementMatcher loaderMatcher = ClassLoaderMatcherConvert.INSTANCE .convert(points.getClassLoaderMatcher()); Set methodMatchers = points.getMethodMatcher(); Set mInfo = methodMatchers.stream().map(matcher -> { Junction bMethodMatcher = MethodMatcherConvert.INSTANCE.convert(matcher); String qualifier = getMethodQualifier(pointsClassName, matcher.getQualifier()); Integer index = QUALIFIER_TO_INDEX.get(qualifier); if (index == null) { // it is unusual for this is a pointcut without interceptor. // maybe there is some error in plugin providers configuration return null; } Builder providerBuilder = INTERCEPTOR_PROVIDERS.get(index); if (providerBuilder == null) { return null; } MethodTransformation mt = new MethodTransformation(index, bMethodMatcher, providerBuilder); if (INDEX_TO_METHOD_TRANSFORMATION.putIfAbsent(index, mt) != null) { log.error("There are duplicate qualifier in Points:{}!", qualifier); } return mt; }).filter(Objects::nonNull).collect(Collectors.toSet()); AgentPlugin plugin = POINTS_TO_PLUGIN.get(pointsClassName); int order = plugin.order(); return ClassTransformation.builder().classMatcher(innerClassMatcher) .hasDynamicField(hasDynamicField) .methodTransformations(mInfo) .classloaderMatcher(loaderMatcher) .typeFieldAccessor(points.getTypeFieldAccessor()) .order(order).build(); } public static int register(InterceptorProvider provider) { String qualifier = provider.getAdviceTo(); // map interceptor/pointcut to plugin AgentPlugin plugin = PLUGIN_CLASSNAME_TO_PLUGIN.get(provider.getPluginClassName()); if (plugin == null) { // code autogenerate issues that are unlikely to occur! throw new RuntimeException(); } QUALIFIER_TO_PLUGIN.putIfAbsent(qualifier, plugin); POINTS_TO_PLUGIN.putIfAbsent(getPointsClassName(qualifier), plugin); // generate index and supplier chain Integer index = QUALIFIER_TO_INDEX.get(provider.getAdviceTo()); if (index == null) { synchronized (QUALIFIER_TO_INDEX) { index = QUALIFIER_TO_INDEX.get(provider.getAdviceTo()); if (index == null) { index = INTERCEPTOR_PROVIDERS.add(ProviderChain.builder()); QUALIFIER_TO_INDEX.putIfAbsent(provider.getAdviceTo(), index); } } } INTERCEPTOR_PROVIDERS.get(index) .addProvider(new ProviderPluginDecorator(plugin, provider)); return index; } public static String getPointsClassName(String name) { int index; if (Strings.isNullOrEmpty(name)) { return "unknown"; } index = name.indexOf(':'); if (index < 0) { return name; } return name.substring(0, index); } public static MethodTransformation getMethodTransformation(int pointcutIndex) { return INDEX_TO_METHOD_TRANSFORMATION.get(pointcutIndex); } public static void addMethodTransformation(int pointcutIndex, MethodTransformation info) { INDEX_TO_METHOD_TRANSFORMATION.putIfAbsent(pointcutIndex, info); } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/transformer/AnnotationTransformer.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.transformer; import com.megaease.easeagent.core.plugin.annotation.EaseAgentInstrumented; import com.megaease.easeagent.core.plugin.matcher.MethodTransformation; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.AsmVisitorWrapper; import net.bytebuddy.asm.MemberAttributeExtension; import net.bytebuddy.description.annotation.AnnotationDescription; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.pool.TypePool; import net.bytebuddy.utility.JavaModule; public class AnnotationTransformer implements AgentBuilder.Transformer { private final AsmVisitorWrapper visitor; private final MethodTransformation methodTransformInfo; private final AnnotationDescription annotation; public AnnotationTransformer(MethodTransformation info) { this.methodTransformInfo = info; this.annotation = AnnotationDescription.Builder .ofType(EaseAgentInstrumented.class) .define("value", info.getIndex()) .build(); MemberAttributeExtension.ForMethod mForMethod = new MemberAttributeExtension.ForMethod() .annotateMethod(this.annotation); this.visitor = new ForMethodDelegate(mForMethod, annotation, methodTransformInfo) .on(methodTransformInfo.getMatcher()); } @Override public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) { return builder.visit(this.visitor); } public static class ForMethodDelegate implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { private final TypeDescription annotation; private final MemberAttributeExtension.ForMethod mForMethod; private final MethodTransformation info; ForMethodDelegate(MemberAttributeExtension.ForMethod mForMethod, AnnotationDescription annotation, MethodTransformation info) { this.annotation = annotation.getAnnotationType(); this.mForMethod = mForMethod; this.info = info; } @Override public MethodVisitor wrap(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, TypePool typePool, int writerFlags, int readerFlags) { AnnotationDescription annotation = instrumentedMethod.getDeclaredAnnotations().ofType(this.annotation); if (annotation != null) { // merge interceptor chain Integer index = annotation.getValue("value").resolve(Integer.class); } return methodVisitor; } public AsmVisitorWrapper on(ElementMatcher matcher) { return new AsmVisitorWrapper.ForDeclaredMethods().invokable(matcher, this); } } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/transformer/CompoundPluginTransformer.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.transformer; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.utility.JavaModule; import java.util.ArrayList; import java.util.List; public class CompoundPluginTransformer implements AgentBuilder.Transformer { private final List transformers; public CompoundPluginTransformer(List transformers) { this.transformers = new ArrayList<>(); for (AgentBuilder.Transformer transformer : transformers) { if (transformer instanceof CompoundPluginTransformer) { this.transformers.addAll(((CompoundPluginTransformer) transformer).transformers); continue; } this.transformers.add(transformer); } } @Override public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) { for (AgentBuilder.Transformer transformer : this.transformers) { builder = transformer.transform(builder, typeDescription, classLoader, module); } return builder; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/transformer/DynamicFieldAdvice.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.transformer; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.field.DynamicFieldAccessor; import com.megaease.easeagent.plugin.field.NullObject; import net.bytebuddy.asm.Advice; public class DynamicFieldAdvice { private DynamicFieldAdvice() { } public static final Logger log = LoggerFactory.getLogger(DynamicFieldAdvice.class); public static class DynamicInstanceInit { private DynamicInstanceInit() { } @Advice.OnMethodExit public static void exit(@Advice.This(optional = true) Object target) { if (target instanceof DynamicFieldAccessor) { DynamicFieldAccessor accessor = (DynamicFieldAccessor) target; if (accessor.getEaseAgent$$DynamicField$$Data() == null) { accessor.setEaseAgent$$DynamicField$$Data(NullObject.NULL); } } } } public static class DynamicClassInit { private DynamicClassInit() { } @Advice.OnMethodExit public static void exit(@Advice.Origin("#m") String method) { } } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/transformer/DynamicFieldTransformer.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.transformer; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.megaease.easeagent.core.plugin.transformer.DynamicFieldAdvice.DynamicInstanceInit; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.field.DynamicFieldAccessor; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.utility.JavaModule; import java.util.concurrent.ConcurrentHashMap; public class DynamicFieldTransformer implements AgentBuilder.Transformer { private static final Logger log = LoggerFactory.getLogger(DynamicFieldTransformer.class); private static final ConcurrentHashMap> FIELD_MAP = new ConcurrentHashMap<>(); private final String fieldName; private final Class accessor; private final AgentBuilder.Transformer.ForAdvice transformer; public DynamicFieldTransformer(String fieldName) { this(fieldName, DynamicFieldAccessor.class); } public DynamicFieldTransformer(String fieldName, Class accessor) { this.fieldName = fieldName; this.accessor = accessor; this.transformer = new AgentBuilder.Transformer .ForAdvice(Advice.withCustomMapping()) .include(getClass().getClassLoader()) .advice(ElementMatchers.isConstructor(), DynamicInstanceInit.class.getName()); } @Override public DynamicType.Builder transform(DynamicType.Builder b, TypeDescription td, ClassLoader cl, JavaModule m) { if (check(td, this.accessor, cl) && this.fieldName != null) { try { b = b.defineField(this.fieldName, Object.class, Opcodes.ACC_PRIVATE) .implement(this.accessor) .intercept(FieldAccessor.ofField(this.fieldName)); } catch (Exception e) { log.debug("Type:{} add extend field again!", td.getName()); } return transformer.transform(b, td, cl, m); } return b; } /** * Avoiding add a accessor interface to a class repeatedly * * @param td represent the class to be enhanced * @param accessor access interface class * @param cl current classloader * @return return true when it is the first time */ private static boolean check(TypeDescription td, Class accessor, ClassLoader cl) { String key = td.getCanonicalName() + accessor.getCanonicalName(); Cache checkCache = FIELD_MAP.get(key); if (checkCache == null) { Cache cache = CacheBuilder.newBuilder().weakKeys().build(); if (cl == null) { cl = Thread.currentThread().getContextClassLoader(); } cache.put(cl, true); checkCache = FIELD_MAP.putIfAbsent(key, cache); if (checkCache == null) { return true; } } return checkCache.getIfPresent(cl) == null; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/transformer/ForAdviceTransformer.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.transformer; import com.megaease.easeagent.core.plugin.CommonInlineAdvice; import com.megaease.easeagent.core.plugin.annotation.Index; import com.megaease.easeagent.core.plugin.matcher.MethodTransformation; import com.megaease.easeagent.core.plugin.registry.AdviceRegistry; import com.megaease.easeagent.core.plugin.transformer.advice.AgentAdvice; import com.megaease.easeagent.core.plugin.transformer.advice.AgentAdvice.OffsetMapping; import com.megaease.easeagent.core.plugin.transformer.advice.AgentForAdvice; import com.megaease.easeagent.core.plugin.transformer.advice.AgentJavaConstantValue; import com.megaease.easeagent.core.plugin.transformer.advice.MethodIdentityJavaConstant; import com.megaease.easeagent.core.plugin.transformer.classloader.CompoundClassloader; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.bytecode.StackManipulation; import net.bytebuddy.utility.JavaModule; public class ForAdviceTransformer implements AgentBuilder.Transformer { private final AgentForAdvice transformer; private final MethodTransformation methodTransformInfo; public ForAdviceTransformer(MethodTransformation methodTransformInfo) { this.methodTransformInfo = methodTransformInfo; MethodIdentityJavaConstant value = new MethodIdentityJavaConstant(methodTransformInfo.getIndex()); StackManipulation stackManipulation = new AgentJavaConstantValue(value, methodTransformInfo.getIndex()); TypeDescription typeDescription = value.getTypeDescription(); OffsetMapping.Factory factory = new OffsetMapping.ForStackManipulation.Factory<>(Index.class, stackManipulation, typeDescription.asGenericType()); this.transformer = new AgentForAdvice(AgentAdvice.withCustomMapping() .bind(factory)) .include(getClass().getClassLoader()) .advice(methodTransformInfo.getMatcher(), CommonInlineAdvice.class.getCanonicalName()); } @Override public DynamicType.Builder transform(DynamicType.Builder b, TypeDescription td, ClassLoader cl, JavaModule m) { CompoundClassloader.compound(this.getClass().getClassLoader(), cl); AdviceRegistry.setCurrentClassLoader(cl); DynamicType.Builder bd = transformer.transform(b, td, cl, m); AdviceRegistry.cleanCurrentClassLoader(); return bd; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/transformer/TypeFieldTransformer.java ================================================ package com.megaease.easeagent.core.plugin.transformer; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.field.TypeFieldGetter; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.utility.JavaModule; import java.util.concurrent.ConcurrentHashMap; public class TypeFieldTransformer implements AgentBuilder.Transformer { private static final Logger log = LoggerFactory.getLogger(TypeFieldTransformer.class); private static final ConcurrentHashMap> FIELD_MAP = new ConcurrentHashMap<>(); private final String fieldName; private final Class accessor; private final AgentBuilder.Transformer.ForAdvice transformer; public TypeFieldTransformer(String fieldName) { this.fieldName = fieldName; this.accessor = TypeFieldGetter.class; this.transformer = new AgentBuilder.Transformer .ForAdvice(Advice.withCustomMapping()) .include(getClass().getClassLoader()); } @Override public DynamicType.Builder transform(DynamicType.Builder b, TypeDescription td, ClassLoader cl, JavaModule m) { if (check(td, this.accessor, cl) && this.fieldName != null) { try { b = b.implement(this.accessor) .intercept(FieldAccessor.ofField(this.fieldName)); } catch (Exception e) { log.debug("Type:{} add extend field again!", td.getName()); } return transformer.transform(b, td, cl, m); } return b; } /** * Avoiding add a accessor interface to a class repeatedly * * @param td represent the class to be enhanced * @param accessor access interface class * @param cl current classloader * @return return true when it is the first time */ private static boolean check(TypeDescription td, Class accessor, ClassLoader cl) { String key = td.getCanonicalName() + accessor.getCanonicalName(); Cache checkCache = FIELD_MAP.get(key); if (checkCache == null) { Cache cache = CacheBuilder.newBuilder().weakKeys().build(); if (cl == null) { cl = Thread.currentThread().getContextClassLoader(); } cache.put(cl, true); checkCache = FIELD_MAP.putIfAbsent(key, cache); if (checkCache == null) { return true; } } return checkCache.getIfPresent(cl) == null; } } ================================================ FILE: core/src/main/java/com/megaease/easeagent/core/plugin/transformer/advice/AgentAdvice.java ================================================ /* * Copyright (c) 2021, MegaEase * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.megaease.easeagent.core.plugin.transformer.advice; import com.megaease.easeagent.core.plugin.registry.AdviceRegistry; import net.bytebuddy.ClassFileVersion; import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.AsmVisitorWrapper; import net.bytebuddy.build.HashCodeAndEqualsPlugin; import net.bytebuddy.description.annotation.AnnotationDescription; import net.bytebuddy.description.enumeration.EnumerationDescription; import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.method.MethodList; import net.bytebuddy.description.method.ParameterDescription; import net.bytebuddy.description.method.ParameterList; import net.bytebuddy.description.type.TypeDefinition; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.ClassFileLocator; import net.bytebuddy.dynamic.TargetType; import net.bytebuddy.dynamic.scaffold.FieldLocator; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.SuperMethodCall; import net.bytebuddy.implementation.bytecode.*; import net.bytebuddy.implementation.bytecode.assign.Assigner; import net.bytebuddy.implementation.bytecode.collection.ArrayAccess; import net.bytebuddy.implementation.bytecode.collection.ArrayFactory; import net.bytebuddy.implementation.bytecode.constant.*; import net.bytebuddy.implementation.bytecode.member.FieldAccess; import net.bytebuddy.implementation.bytecode.member.MethodInvocation; import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess; import net.bytebuddy.jar.asm.*; import net.bytebuddy.utility.CompoundList; import net.bytebuddy.utility.JavaConstant; import net.bytebuddy.utility.JavaType; import net.bytebuddy.utility.OpenedClassReader; import net.bytebuddy.utility.visitor.ExceptionTableSensitiveMethodVisitor; import net.bytebuddy.utility.visitor.FramePaddingMethodVisitor; import net.bytebuddy.utility.visitor.LineNumberPrependingMethodVisitor; import net.bytebuddy.utility.visitor.StackAwareMethodVisitor; import java.io.IOException; import java.io.Serializable; import java.lang.annotation.*; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.*; import static net.bytebuddy.matcher.ElementMatchers.*; @SuppressWarnings("unused, rawtypes, unchecked") public class AgentAdvice extends Advice { private static final ClassReader UNDEFINED = null; /** * A reference to the {@link OnMethodEnter#skipOn()} method. */ private static final MethodDescription.InDefinedShape SKIP_ON; /** * A reference to the {@link OnMethodEnter#prependLineNumber()} method. */ private static final MethodDescription.InDefinedShape PREPEND_LINE_NUMBER; /** * A reference to the {@link OnMethodEnter#inline()} method. */ private static final MethodDescription.InDefinedShape INLINE_ENTER; /** * A reference to the {@link OnMethodEnter#suppress()} method. */ private static final MethodDescription.InDefinedShape SUPPRESS_ENTER; /** * A reference to the {@link OnMethodExit#repeatOn()} method. */ private static final MethodDescription.InDefinedShape REPEAT_ON; /** * A reference to the {@link OnMethodExit#onThrowable()} method. */ private static final MethodDescription.InDefinedShape ON_THROWABLE; /** * A reference to the {@link OnMethodExit#backupArguments()} method. */ private static final MethodDescription.InDefinedShape BACKUP_ARGUMENTS; /** * A reference to the {@link OnMethodExit#inline()} method. */ private static final MethodDescription.InDefinedShape INLINE_EXIT; /** * A reference to the {@link OnMethodExit#suppress()} method. */ private static final MethodDescription.InDefinedShape SUPPRESS_EXIT; static { MethodList enter = TypeDescription.ForLoadedType.of(OnMethodEnter.class).getDeclaredMethods(); SKIP_ON = enter.filter(named("skipOn")).getOnly(); PREPEND_LINE_NUMBER = enter.filter(named("prependLineNumber")).getOnly(); INLINE_ENTER = enter.filter(named("inline")).getOnly(); SUPPRESS_ENTER = enter.filter(named("suppress")).getOnly(); MethodList exit = TypeDescription.ForLoadedType.of(OnMethodExit.class).getDeclaredMethods(); REPEAT_ON = exit.filter(named("repeatOn")).getOnly(); ON_THROWABLE = exit.filter(named("onThrowable")).getOnly(); BACKUP_ARGUMENTS = exit.filter(named("backupArguments")).getOnly(); INLINE_EXIT = exit.filter(named("inline")).getOnly(); SUPPRESS_EXIT = exit.filter(named("suppress")).getOnly(); } /** * The dispatcher for instrumenting the instrumented method upon entering. */ private final Dispatcher.Resolved.ForMethodEnter methodEnter; /** * The dispatcher for instrumenting the instrumented method upon exiting. */ private final Dispatcher.Resolved.ForMethodExit methodExit; private final Dispatcher.Resolved.ForMethodExit methodExitNonThrowable; /** * The assigner to use. */ private final Assigner assigner; /** * The exception handler to apply. */ private final ExceptionHandler exceptionHandler; /** * The delegate implementation to apply if this advice is used as an instrumentation. */ private final Implementation delegate; /** * Creates a new advice. * * @param methodEnter The dispatcher for instrumenting the instrumented method upon entering. * @param methodExit The dispatcher for instrumenting the instrumented method upon exiting. */ protected AgentAdvice(Dispatcher.Resolved.ForMethodEnter methodEnter, // Dispatcher.Resolved.ForMethodExit methodExitNonThrowable, Dispatcher.Resolved.ForMethodExit methodExit) { this(methodEnter, methodExit, null, Assigner.DEFAULT, ExceptionHandler.Default.SUPPRESSING, SuperMethodCall.INSTANCE); } protected AgentAdvice(Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExitNonThrowable, Dispatcher.Resolved.ForMethodExit methodExit) { this(methodEnter, methodExit, methodExitNonThrowable, Assigner.DEFAULT, ExceptionHandler.Default.SUPPRESSING, SuperMethodCall.INSTANCE); } /** * Creates a new advice. * * @param methodEnter The dispatcher for instrumenting the instrumented method upon entering. * @param methodExit The dispatcher for instrumenting the instrumented method upon exiting. * @param assigner The assigner to use. * @param exceptionHandler The exception handler to apply. * @param delegate The delegate implementation to apply if this advice is used as an instrumentation. */ private AgentAdvice(Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit, Dispatcher.Resolved.ForMethodExit methodExitNonThrowable, Assigner assigner, ExceptionHandler exceptionHandler, Implementation delegate) { super(null, null); this.methodEnter = methodEnter; this.methodExit = methodExit; this.methodExitNonThrowable = methodExitNonThrowable; this.assigner = assigner; this.exceptionHandler = exceptionHandler; this.delegate = delegate; } private static boolean isNoExceptionHandler(TypeDescription t) { return t.getName().endsWith("NoExceptionHandler"); // return t.represents(NoExceptionHandler.class) || t.represents() } /** * Creates a new advice. * * @param advice A description of the type declaring the advice. * @param postProcessorFactory The post processor factory to use. * @param classFileLocator The class file locator for locating the advisory class's class file. * @param userFactories A list of custom factories for user generated offset mappings. * @param delegator The delegator to use. * @return A method visitor wrapper representing the supplied advice. */ protected static AgentAdvice tto(TypeDescription advice, PostProcessor.Factory postProcessorFactory, ClassFileLocator classFileLocator, List> userFactories, Delegator delegator) { Dispatcher.Unresolved methodEnter = Dispatcher.Inactive.INSTANCE, methodExit = Dispatcher.Inactive.INSTANCE, methodExitNoException = Dispatcher.Inactive.INSTANCE; for (MethodDescription.InDefinedShape methodDescription : advice.getDeclaredMethods()) { methodEnter = locate(OnMethodEnter.class, INLINE_ENTER, methodEnter, methodDescription, delegator); AnnotationDescription.Loadable al = methodDescription.getDeclaredAnnotations().ofType(OnMethodExit.class); if (al != null) { TypeDescription throwable = al.getValue(ON_THROWABLE).resolve(TypeDescription.class); if (isNoExceptionHandler(throwable)) { methodExitNoException = locate(OnMethodExit.class, INLINE_EXIT, methodExitNoException, methodDescription, delegator); } else { methodExit = locate(OnMethodExit.class, INLINE_EXIT, methodExit, methodDescription, delegator); } } } if (methodExit == Dispatcher.Inactive.INSTANCE) { methodEnter = methodExitNoException; } if (!methodEnter.isAlive() && !methodExit.isAlive() && !methodExitNoException.isAlive()) { throw new IllegalArgumentException("No advice defined by " + advice); } try { ClassReader classReader = methodEnter.isBinary() || methodExit.isBinary() ? OpenedClassReader.of(classFileLocator.locate(advice.getName()).resolve()) : UNDEFINED; return new AgentAdvice(methodEnter.asMethodEnter(userFactories, classReader, methodExit, postProcessorFactory), methodExitNoException.asMethodExit(userFactories, classReader, methodEnter, postProcessorFactory), methodExit.asMethodExit(userFactories, classReader, methodEnter, postProcessorFactory)); } catch (IOException exception) { throw new IllegalStateException("Error reading class file of " + advice, exception); } } public static AgentAdvice.WithCustomMapping withCustomMapping() { return new WithCustomMapping(); } private static Dispatcher.Unresolved locate(Class type, MethodDescription.InDefinedShape property, Dispatcher.Unresolved dispatcher, MethodDescription.InDefinedShape methodDescription, Delegator delegator) { AnnotationDescription annotation = methodDescription.getDeclaredAnnotations().ofType(type); if (annotation == null) { return dispatcher; } else if (dispatcher.isAlive()) { throw new IllegalStateException("Duplicate advice for " + dispatcher + " and " + methodDescription); } else if (!methodDescription.isStatic()) { throw new IllegalStateException("Advice for " + methodDescription + " is not static"); } else { return new Dispatcher.Inlining(methodDescription); } } /** * Configures this advice to use the specified assigner. Any previous or default assigner is replaced. * * @param assigner The assigner to use, * @return A version of this advice that uses the specified assigner. */ @Override public AgentAdvice withAssigner(Assigner assigner) { return new AgentAdvice(methodEnter, methodExitNonThrowable, methodExit, assigner, exceptionHandler, delegate); } /** * Configures this advice to execute the given exception handler upon a suppressed exception. The stack manipulation is executed with a * {@link Throwable} instance on the operand stack. The stack must be empty upon completing the exception handler. * * @param exceptionHandler The exception handler to apply. * @return A version of this advice that applies the supplied exception handler. */ @Override public AgentAdvice withExceptionHandler(ExceptionHandler exceptionHandler) { return new AgentAdvice(methodEnter, methodExitNonThrowable, methodExit, assigner, exceptionHandler, delegate); } /** * A builder step for creating an {@link Advice} that uses custom mappings of annotations to constant pool values. */ @HashCodeAndEqualsPlugin.Enhance public static class WithCustomMapping extends Advice.WithCustomMapping { /** * The post processor factory to apply. */ private final PostProcessor.Factory postProcessorFactory; /** * The delegator to use. */ private final Delegator delegator; /** * A map containing dynamically computed constant pool values that are mapped by their triggering annotation type. */ private final Map, OffsetMapping.Factory> offsetMappings; /** * Creates a new custom mapping builder step without including any custom mappings. */ public WithCustomMapping() { this(PostProcessor.NoOp.INSTANCE, Collections.emptyMap(), Delegator.ForStaticInvocation.INSTANCE); } /** * Creates a new custom mapping builder step with the given custom mappings. * * @param postProcessorFactory The post processor factory to apply. * @param offsetMappings A map containing dynamically computed constant pool values that are mapped by their triggering annotation type. * @param delegator The delegator to use. */ protected WithCustomMapping(PostProcessor.Factory postProcessorFactory, Map, OffsetMapping.Factory> offsetMappings, Delegator delegator) { this.postProcessorFactory = postProcessorFactory; this.offsetMappings = offsetMappings; this.delegator = delegator; } /** * Binds the supplied annotation to a type constant of the supplied value. Constants can be strings, method handles, method types * and any primitive or the value {@code null}. * * @param type The type of the annotation being bound. * @param value The value to bind to the annotation. * @param The annotation type. * @return A new builder for an advice that considers the supplied annotation type during binding. */ @Override public WithCustomMapping bind(Class type, Object value) { return bind(OffsetMapping.ForStackManipulation.Factory.of(type, value)); } /** * Binds an annotation to a dynamically computed value. Whenever the {@link Advice} component discovers the given annotation on * a parameter of an advice method, the dynamic value is asked to provide a value that is then assigned to the parameter in question. * * @param offsetMapping The dynamic value that is computed for binding the parameter to a value. * @return A new builder for an advice that considers the supplied annotation type during binding. */ public WithCustomMapping bind(OffsetMapping.Factory offsetMapping) { Map, OffsetMapping.Factory> offsetMappings = new HashMap<>(this.offsetMappings); if (!offsetMapping.getAnnotationType().isAnnotation()) { throw new IllegalArgumentException("Not an annotation type: " + offsetMapping.getAnnotationType()); } else if (offsetMappings.put(offsetMapping.getAnnotationType(), offsetMapping) != null) { throw new IllegalArgumentException("Annotation type already mapped: " + offsetMapping.getAnnotationType()); } return new WithCustomMapping(postProcessorFactory, offsetMappings, delegator); } /** * Implements advice where every matched method is advised by the given type's advisory methods. * * @param advice A description of the type declaring the advice. * @param classFileLocator The class file locator for locating the advisory class's class file. * @return A method visitor wrapper representing the supplied advice. */ @Override public AgentAdvice to(TypeDescription advice, ClassFileLocator classFileLocator) { return AgentAdvice.tto(advice, postProcessorFactory, classFileLocator, new ArrayList<>(offsetMappings.values()), delegator); } } /** * Wraps the method visitor to implement this advice. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param methodVisitor The method visitor to write to. * @param implementationContext The implementation context to use. * @param writerFlags The ASM writer flags to use. * @param readerFlags The, plies this advice. */ @Override protected MethodVisitor doWrap(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, int writerFlags, int readerFlags) { Dispatcher.Resolved.ForMethodExit exit; if (instrumentedMethod.isConstructor()) { exit = methodExitNonThrowable; } else { exit = methodExit; } if (AdviceRegistry.check(instrumentedType, instrumentedMethod, methodEnter, exit) == 0) { return methodVisitor; } methodVisitor = new FramePaddingMethodVisitor(methodEnter.isPrependLineNumber() ? new LineNumberPrependingMethodVisitor(methodVisitor) : methodVisitor); if (instrumentedMethod.isConstructor()) { return new WithExitAdvice.WithoutExceptionHandling(methodVisitor, implementationContext, assigner, exceptionHandler.resolve(instrumentedMethod, instrumentedType), instrumentedType, instrumentedMethod, methodEnter, methodExitNonThrowable, writerFlags, readerFlags); } else { return new WithExitAdvice.WithExceptionHandling(methodVisitor, implementationContext, assigner, exceptionHandler.resolve(instrumentedMethod, instrumentedType), instrumentedType, instrumentedMethod, methodEnter, methodExit, writerFlags, readerFlags, methodExit.getThrowable()); } } /** * A method visitor that weaves the advice methods' byte codes. */ protected abstract static class AdviceVisitor extends ExceptionTableSensitiveMethodVisitor implements Dispatcher.RelocationHandler.Relocation { /** * The expected index for the {@code this} variable. */ private static final int THIS_VARIABLE_INDEX = 0; /** * The expected name for the {@code this} variable. */ private static final String THIS_VARIABLE_NAME = "this"; /** * A description of the instrumented method. */ protected final MethodDescription instrumentedMethod; /** * A label that indicates the start of the preparation of a user method execution. */ private final Label preparationStart; /** * The dispatcher to be used for method enter. */ private final Dispatcher.Bound methodEnter; /** * The dispatcher to be used for method exit. */ protected final Dispatcher.Bound methodExit; /** * The handler for accessing arguments of the method's local variable array. */ protected final ArgumentHandler.ForInstrumentedMethod argumentHandler; /** * A handler for computing the method size requirements. */ protected final MethodSizeHandler.ForInstrumentedMethod methodSizeHandler; /** * A handler for translating and injecting stack map frames. */ protected final StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler; /** * Creates a new advice visitor. * * @param methodVisitor The actual method visitor that is underlying this method visitor to which all instructions are written. * @param implementationContext The implementation context to use. * @param assigner The assigner to use. * @param exceptionHandler The stack manipulation to apply within a suppression handler. * @param instrumentedType A description of the instrumented type. * @param instrumentedMethod The instrumented method. * @param methodEnter The method enter advice. * @param methodExit The method exit advice. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param writerFlags The ASM writer flags that were set. * @param readerFlags The ASM reader flags that were set. */ protected AdviceVisitor(MethodVisitor methodVisitor, Context implementationContext, Assigner assigner, StackManipulation exceptionHandler, TypeDescription instrumentedType, MethodDescription instrumentedMethod, Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit, List postMethodTypes, int writerFlags, int readerFlags) { super(OpenedClassReader.ASM_API, methodVisitor); this.instrumentedMethod = instrumentedMethod; preparationStart = new Label(); SortedMap namedTypes = new TreeMap<>(); namedTypes.putAll(methodEnter.getNamedTypes()); namedTypes.putAll(methodExit.getNamedTypes()); argumentHandler = methodExit.getArgumentHandlerFactory().resolve(instrumentedMethod, methodEnter.getAdviceType(), methodExit.getAdviceType(), namedTypes); List initialTypes = CompoundList.of(methodExit.getAdviceType().represents(void.class) ? Collections.emptyList() : Collections.singletonList(methodExit.getAdviceType().asErasure()), argumentHandler.getNamedTypes()); List latentTypes = methodEnter.getActualAdviceType().represents(void.class) ? Collections.emptyList() : Collections.singletonList(methodEnter.getActualAdviceType().asErasure()); List preMethodTypes = methodEnter.getAdviceType().represents(void.class) ? Collections.emptyList() : Collections.singletonList(methodEnter.getAdviceType().asErasure()); methodSizeHandler = MethodSizeHandler.Default.of(instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes, argumentHandler.isCopyingArguments(), writerFlags); stackMapFrameHandler = StackMapFrameHandler.Default.of(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, methodExit.isAlive(), argumentHandler.isCopyingArguments(), implementationContext.getClassFileVersion(), writerFlags, readerFlags); this.methodEnter = methodEnter.bind(instrumentedType, instrumentedMethod, methodVisitor, implementationContext, assigner, argumentHandler, methodSizeHandler, stackMapFrameHandler, exceptionHandler, this); this.methodExit = methodExit.bind(instrumentedType, instrumentedMethod, methodVisitor, implementationContext, assigner, argumentHandler, methodSizeHandler, stackMapFrameHandler, exceptionHandler, new ForLabel(preparationStart)); } @Override protected void onAfterExceptionTable() { methodEnter.prepare(); onUserPrepare(); methodExit.prepare(); methodEnter.initialize(); methodExit.initialize(); stackMapFrameHandler.injectInitializationFrame(mv); methodEnter.apply(); mv.visitLabel(preparationStart); methodSizeHandler.requireStackSize(argumentHandler.prepare(mv)); stackMapFrameHandler.injectStartFrame(mv); onUserStart(); } /** * Invoked when the user method's exception handler (if any) is supposed to be prepared. */ protected abstract void onUserPrepare(); /** * Writes the advice for entering the instrumented method. */ protected abstract void onUserStart(); @Override protected void onVisitVarInsn(int opcode, int offset) { mv.visitVarInsn(opcode, argumentHandler.argument(offset)); } @Override protected void onVisitIincInsn(int offset, int increment) { mv.visitIincInsn(argumentHandler.argument(offset), increment); } @Override public void onVisitFrame(int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack) { stackMapFrameHandler.translateFrame(mv, type, localVariableLength, localVariable, stackSize, stack); } @Override public void visitMaxs(int stackSize, int localVariableLength) { onUserEnd(); mv.visitMaxs(methodSizeHandler.compoundStackSize(stackSize), methodSizeHandler.compoundLocalVariableLength(localVariableLength)); } @Override public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { // The 'this' variable is exempt from remapping as it is assumed immutable and remapping it confuses debuggers to not display the variable. mv.visitLocalVariable(name, descriptor, signature, start, end, index == THIS_VARIABLE_INDEX && THIS_VARIABLE_NAME.equals(name) ? index : argumentHandler.variable(index)); } @Override public AnnotationVisitor visitLocalVariableAnnotation(int typeReference, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) { int[] translated = new int[index.length]; for (int anIndex = 0; anIndex < index.length; anIndex++) { translated[anIndex] = argumentHandler.variable(index[anIndex]); } return mv.visitLocalVariableAnnotation(typeReference, typePath, start, end, translated, descriptor, visible); } /** * Writes the advice for completing the instrumented method. */ protected abstract void onUserEnd(); } /** * An advice visitor that does not apply exit advice. */ protected static class WithoutExitAdvice extends AdviceVisitor { /** * Creates an advice visitor that does not apply exit advice. * * @param methodVisitor The method visitor for the instrumented method. * @param implementationContext The implementation context to use. * @param assigner The assigner to use. * @param exceptionHandler The stack manipulation to apply within a suppression handler. * @param instrumentedType A description of the instrumented type. * @param instrumentedMethod A description of the instrumented method. * @param methodEnter The dispatcher to be used for method enter. * @param writerFlags The ASM writer flags that were set. * @param readerFlags The ASM reader flags that were set. */ protected WithoutExitAdvice(MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, StackManipulation exceptionHandler, TypeDescription instrumentedType, MethodDescription instrumentedMethod, Dispatcher.Resolved.ForMethodEnter methodEnter, int writerFlags, int readerFlags) { super(methodVisitor, implementationContext, assigner, exceptionHandler, instrumentedType, instrumentedMethod, methodEnter, Dispatcher.Inactive.INSTANCE, Collections.emptyList(), writerFlags, readerFlags); } /** * {@inheritDoc} */ public void apply(MethodVisitor methodVisitor) { if (instrumentedMethod.getReturnType().represents(boolean.class) || instrumentedMethod.getReturnType().represents(byte.class) || instrumentedMethod.getReturnType().represents(short.class) || instrumentedMethod.getReturnType().represents(char.class) || instrumentedMethod.getReturnType().represents(int.class)) { methodVisitor.visitInsn(Opcodes.ICONST_0); methodVisitor.visitInsn(Opcodes.IRETURN); } else if (instrumentedMethod.getReturnType().represents(long.class)) { methodVisitor.visitInsn(Opcodes.LCONST_0); methodVisitor.visitInsn(Opcodes.LRETURN); } else if (instrumentedMethod.getReturnType().represents(float.class)) { methodVisitor.visitInsn(Opcodes.FCONST_0); methodVisitor.visitInsn(Opcodes.FRETURN); } else if (instrumentedMethod.getReturnType().represents(double.class)) { methodVisitor.visitInsn(Opcodes.DCONST_0); methodVisitor.visitInsn(Opcodes.DRETURN); } else if (instrumentedMethod.getReturnType().represents(void.class)) { methodVisitor.visitInsn(Opcodes.RETURN); } else { methodVisitor.visitInsn(Opcodes.ACONST_NULL); methodVisitor.visitInsn(Opcodes.ARETURN); } } @Override protected void onUserPrepare() { /* do nothing */ } @Override protected void onUserStart() { /* do nothing */ } @Override protected void onUserEnd() { /* do nothing */ } } /** * An advice visitor that applies exit advice. */ protected abstract static class WithExitAdvice extends AdviceVisitor { /** * Indicates the handler for the value returned by the advice method. */ protected final Label returnHandler; /** * Creates an advice visitor that applies exit advice. * * @param methodVisitor The method visitor for the instrumented method. * @param implementationContext The implementation context to use. * @param assigner The assigner to use. * @param exceptionHandler The stack manipulation to apply within a suppression handler. * @param instrumentedType A description of the instrumented type. * @param instrumentedMethod A description of the instrumented method. * @param methodEnter The dispatcher to be used for method enter. * @param methodExit The dispatcher to be used for method exit. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param writerFlags The ASM writer flags that were set. * @param readerFlags The ASM reader flags that were set. */ protected WithExitAdvice(MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, StackManipulation exceptionHandler, TypeDescription instrumentedType, MethodDescription instrumentedMethod, Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit, List postMethodTypes, int writerFlags, int readerFlags) { super(new StackAwareMethodVisitor(methodVisitor, instrumentedMethod), implementationContext, assigner, exceptionHandler, instrumentedType, instrumentedMethod, methodEnter, methodExit, postMethodTypes, writerFlags, readerFlags); returnHandler = new Label(); } /** * {@inheritDoc} */ public void apply(MethodVisitor methodVisitor) { if (instrumentedMethod.getReturnType().represents(boolean.class) || instrumentedMethod.getReturnType().represents(byte.class) || instrumentedMethod.getReturnType().represents(short.class) || instrumentedMethod.getReturnType().represents(char.class) || instrumentedMethod.getReturnType().represents(int.class)) { methodVisitor.visitInsn(Opcodes.ICONST_0); } else if (instrumentedMethod.getReturnType().represents(long.class)) { methodVisitor.visitInsn(Opcodes.LCONST_0); } else if (instrumentedMethod.getReturnType().represents(float.class)) { methodVisitor.visitInsn(Opcodes.FCONST_0); } else if (instrumentedMethod.getReturnType().represents(double.class)) { methodVisitor.visitInsn(Opcodes.DCONST_0); } else if (!instrumentedMethod.getReturnType().represents(void.class)) { methodVisitor.visitInsn(Opcodes.ACONST_NULL); } methodVisitor.visitJumpInsn(Opcodes.GOTO, returnHandler); } @Override protected void onVisitInsn(int opcode) { switch (opcode) { case Opcodes.RETURN: ((StackAwareMethodVisitor) mv).drainStack(); break; case Opcodes.IRETURN: methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.ISTORE, Opcodes.ILOAD, StackSize.SINGLE)); break; case Opcodes.FRETURN: methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.FSTORE, Opcodes.FLOAD, StackSize.SINGLE)); break; case Opcodes.DRETURN: methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.DSTORE, Opcodes.DLOAD, StackSize.DOUBLE)); break; case Opcodes.LRETURN: methodSizeHandler.requireLocalVariableLength((((StackAwareMethodVisitor) mv).drainStack(Opcodes.LSTORE, Opcodes.LLOAD, StackSize.DOUBLE))); break; case Opcodes.ARETURN: methodSizeHandler.requireLocalVariableLength((((StackAwareMethodVisitor) mv).drainStack(Opcodes.ASTORE, Opcodes.ALOAD, StackSize.SINGLE))); break; default: mv.visitInsn(opcode); return; } mv.visitJumpInsn(Opcodes.GOTO, returnHandler); } @Override protected void onUserEnd() { mv.visitLabel(returnHandler); onUserReturn(); stackMapFrameHandler.injectCompletionFrame(mv); methodExit.apply(); onExitAdviceReturn(); if (instrumentedMethod.getReturnType().represents(boolean.class) || instrumentedMethod.getReturnType().represents(byte.class) || instrumentedMethod.getReturnType().represents(short.class) || instrumentedMethod.getReturnType().represents(char.class) || instrumentedMethod.getReturnType().represents(int.class)) { mv.visitVarInsn(Opcodes.ILOAD, argumentHandler.returned()); mv.visitInsn(Opcodes.IRETURN); } else if (instrumentedMethod.getReturnType().represents(long.class)) { mv.visitVarInsn(Opcodes.LLOAD, argumentHandler.returned()); mv.visitInsn(Opcodes.LRETURN); } else if (instrumentedMethod.getReturnType().represents(float.class)) { mv.visitVarInsn(Opcodes.FLOAD, argumentHandler.returned()); mv.visitInsn(Opcodes.FRETURN); } else if (instrumentedMethod.getReturnType().represents(double.class)) { mv.visitVarInsn(Opcodes.DLOAD, argumentHandler.returned()); mv.visitInsn(Opcodes.DRETURN); } else if (!instrumentedMethod.getReturnType().represents(void.class)) { mv.visitVarInsn(Opcodes.ALOAD, argumentHandler.returned()); mv.visitInsn(Opcodes.ARETURN); } else { mv.visitInsn(Opcodes.RETURN); } methodSizeHandler.requireStackSize(instrumentedMethod.getReturnType().getStackSize().getSize()); } /** * Invoked after the user method has returned. */ protected abstract void onUserReturn(); /** * Invoked after the exit advice method has returned. */ protected abstract void onExitAdviceReturn(); /** * An advice visitor that does not capture exceptions. */ protected static class WithoutExceptionHandling extends WithExitAdvice { /** * Creates a new advice visitor that does not capture exceptions. * * @param methodVisitor The method visitor for the instrumented method. * @param implementationContext The implementation context to use. * @param assigner The assigner to use. * @param exceptionHandler The stack manipulation to apply within a suppression handler. * @param instrumentedType A description of the instrumented type. * @param instrumentedMethod A description of the instrumented method. * @param methodEnter The dispatcher to be used for method enter. * @param methodExit The dispatcher to be used for method exit. * @param writerFlags The ASM writer flags that were set. * @param readerFlags The ASM reader flags that were set. */ protected WithoutExceptionHandling(MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, StackManipulation exceptionHandler, TypeDescription instrumentedType, MethodDescription instrumentedMethod, Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit, int writerFlags, int readerFlags) { super(methodVisitor, implementationContext, assigner, exceptionHandler, instrumentedType, instrumentedMethod, methodEnter, methodExit, instrumentedMethod.getReturnType().represents(void.class) ? Collections.emptyList() : Collections.singletonList(instrumentedMethod.getReturnType().asErasure()), writerFlags, readerFlags); } @Override protected void onUserPrepare() { /* do nothing */ } @Override protected void onUserStart() { /* do nothing */ } @Override protected void onUserReturn() { if (instrumentedMethod.getReturnType().represents(boolean.class) || instrumentedMethod.getReturnType().represents(byte.class) || instrumentedMethod.getReturnType().represents(short.class) || instrumentedMethod.getReturnType().represents(char.class) || instrumentedMethod.getReturnType().represents(int.class)) { stackMapFrameHandler.injectReturnFrame(mv); mv.visitVarInsn(Opcodes.ISTORE, argumentHandler.returned()); } else if (instrumentedMethod.getReturnType().represents(long.class)) { stackMapFrameHandler.injectReturnFrame(mv); mv.visitVarInsn(Opcodes.LSTORE, argumentHandler.returned()); } else if (instrumentedMethod.getReturnType().represents(float.class)) { stackMapFrameHandler.injectReturnFrame(mv); mv.visitVarInsn(Opcodes.FSTORE, argumentHandler.returned()); } else if (instrumentedMethod.getReturnType().represents(double.class)) { stackMapFrameHandler.injectReturnFrame(mv); mv.visitVarInsn(Opcodes.DSTORE, argumentHandler.returned()); } else if (!instrumentedMethod.getReturnType().represents(void.class)) { stackMapFrameHandler.injectReturnFrame(mv); mv.visitVarInsn(Opcodes.ASTORE, argumentHandler.returned()); } } @Override protected void onExitAdviceReturn() { /* do nothing */ } } /** * An advice visitor that captures exceptions by weaving try-catch blocks around user code. */ protected static class WithExceptionHandling extends WithExitAdvice { /** * The type of the handled throwable type for which this advice is invoked. */ private final TypeDescription throwable; /** * Indicates the exception handler. */ private final Label exceptionHandler; /** * Indicates the start of the user method. */ protected final Label userStart; /** * Creates a new advice visitor that captures exception by weaving try-catch blocks around user code. * * @param methodVisitor The method visitor for the instrumented method. * @param instrumentedType A description of the instrumented type. * @param implementationContext The implementation context to use. * @param assigner The assigner to use. * @param exceptionHandler The stack manipulation to apply within a suppression handler. * @param instrumentedMethod A description of the instrumented method. * @param methodEnter The dispatcher to be used for method enter. * @param methodExit The dispatcher to be used for method exit. * @param writerFlags The ASM writer flags that were set. * @param readerFlags The ASM reader flags that were set. * @param throwable The type of the handled throwable type for which this advice is invoked. */ protected WithExceptionHandling(MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, StackManipulation exceptionHandler, TypeDescription instrumentedType, MethodDescription instrumentedMethod, Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit, int writerFlags, int readerFlags, TypeDescription throwable) { super(methodVisitor, implementationContext, assigner, exceptionHandler, instrumentedType, instrumentedMethod, methodEnter, methodExit, instrumentedMethod.getReturnType().represents(void.class) ? Collections.singletonList(TypeDescription.THROWABLE) : Arrays.asList(instrumentedMethod.getReturnType().asErasure(), TypeDescription.THROWABLE), writerFlags, readerFlags); this.throwable = throwable; this.exceptionHandler = new Label(); userStart = new Label(); } @Override protected void onUserPrepare() { mv.visitTryCatchBlock(userStart, returnHandler, exceptionHandler, throwable.getInternalName()); } @Override protected void onUserStart() { mv.visitLabel(userStart); } @Override protected void onUserReturn() { stackMapFrameHandler.injectReturnFrame(mv); if (instrumentedMethod.getReturnType().represents(boolean.class) || instrumentedMethod.getReturnType().represents(byte.class) || instrumentedMethod.getReturnType().represents(short.class) || instrumentedMethod.getReturnType().represents(char.class) || instrumentedMethod.getReturnType().represents(int.class)) { mv.visitVarInsn(Opcodes.ISTORE, argumentHandler.returned()); } else if (instrumentedMethod.getReturnType().represents(long.class)) { mv.visitVarInsn(Opcodes.LSTORE, argumentHandler.returned()); } else if (instrumentedMethod.getReturnType().represents(float.class)) { mv.visitVarInsn(Opcodes.FSTORE, argumentHandler.returned()); } else if (instrumentedMethod.getReturnType().represents(double.class)) { mv.visitVarInsn(Opcodes.DSTORE, argumentHandler.returned()); } else if (!instrumentedMethod.getReturnType().represents(void.class)) { mv.visitVarInsn(Opcodes.ASTORE, argumentHandler.returned()); } mv.visitInsn(Opcodes.ACONST_NULL); mv.visitVarInsn(Opcodes.ASTORE, argumentHandler.thrown()); Label endOfHandler = new Label(); mv.visitJumpInsn(Opcodes.GOTO, endOfHandler); mv.visitLabel(exceptionHandler); stackMapFrameHandler.injectExceptionFrame(mv); mv.visitVarInsn(Opcodes.ASTORE, argumentHandler.thrown()); if (instrumentedMethod.getReturnType().represents(boolean.class) || instrumentedMethod.getReturnType().represents(byte.class) || instrumentedMethod.getReturnType().represents(short.class) || instrumentedMethod.getReturnType().represents(char.class) || instrumentedMethod.getReturnType().represents(int.class)) { mv.visitInsn(Opcodes.ICONST_0); mv.visitVarInsn(Opcodes.ISTORE, argumentHandler.returned()); } else if (instrumentedMethod.getReturnType().represents(long.class)) { mv.visitInsn(Opcodes.LCONST_0); mv.visitVarInsn(Opcodes.LSTORE, argumentHandler.returned()); } else if (instrumentedMethod.getReturnType().represents(float.class)) { mv.visitInsn(Opcodes.FCONST_0); mv.visitVarInsn(Opcodes.FSTORE, argumentHandler.returned()); } else if (instrumentedMethod.getReturnType().represents(double.class)) { mv.visitInsn(Opcodes.DCONST_0); mv.visitVarInsn(Opcodes.DSTORE, argumentHandler.returned()); } else if (!instrumentedMethod.getReturnType().represents(void.class)) { mv.visitInsn(Opcodes.ACONST_NULL); mv.visitVarInsn(Opcodes.ASTORE, argumentHandler.returned()); } mv.visitLabel(endOfHandler); methodSizeHandler.requireStackSize(StackSize.SINGLE.getSize()); } @Override protected void onExitAdviceReturn() { mv.visitVarInsn(Opcodes.ALOAD, argumentHandler.thrown()); Label endOfHandler = new Label(); mv.visitJumpInsn(Opcodes.IFNULL, endOfHandler); mv.visitVarInsn(Opcodes.ALOAD, argumentHandler.thrown()); mv.visitInsn(Opcodes.ATHROW); mv.visitLabel(endOfHandler); stackMapFrameHandler.injectPostCompletionFrame(mv); } } } /** * A dispatcher for implementing advice. */ public interface Dispatcher { /** * Indicates that a method does not represent advice and does not need to be visited. */ MethodVisitor IGNORE_METHOD = null; /** * Expresses that an annotation should not be visited. */ AnnotationVisitor IGNORE_ANNOTATION = null; /** * Returns {@code true} if this dispatcher is alive. * * @return {@code true} if this dispatcher is alive. */ boolean isAlive(); /** * The type that is produced as a result of executing this advice method. * * @return A description of the type that is produced by this advice method. */ TypeDefinition getAdviceType(); /** * A dispatcher that is not yet resolved. */ interface Unresolved extends Dispatcher { /** * Indicates that this dispatcher requires access to the class file declaring the advice method. * * @return {@code true} if this dispatcher requires access to the advice method's class file. */ boolean isBinary(); /** * Returns the named types declared by this enter advice. * * @return The named types declared by this enter advice. */ Map getNamedTypes(); /** * Resolves this dispatcher as a dispatcher for entering a method. * * @param userFactories A list of custom factories for binding parameters of an advice method. * @param classReader A class reader to query for a class file which might be {@code null} if this dispatcher is not binary. * @param methodExit The unresolved dispatcher for the method exit advice. * @param postProcessorFactory The post processor factory to use. * @return This dispatcher as a dispatcher for entering a method. */ Resolved.ForMethodEnter asMethodEnter(List> userFactories, ClassReader classReader, Unresolved methodExit, PostProcessor.Factory postProcessorFactory); /** * Resolves this dispatcher as a dispatcher for exiting a method. * * @param userFactories A list of custom factories for binding parameters of an advice method. * @param classReader A class reader to query for a class file which might be {@code null} if this dispatcher is not binary. * @param methodEnter The unresolved dispatcher for the method enter advice. * @param postProcessorFactory The post processor factory to use. * @return This dispatcher as a dispatcher for exiting a method. */ Resolved.ForMethodExit asMethodExit(List> userFactories, ClassReader classReader, Unresolved methodEnter, PostProcessor.Factory postProcessorFactory); } /** * A suppression handler for optionally suppressing exceptions. */ interface SuppressionHandler { /** * Binds the suppression handler for instrumenting a specific method. * * @param exceptionHandler The stack manipulation to apply within a suppression handler. * @return A bound version of the suppression handler. */ Bound bind(StackManipulation exceptionHandler); /** * A bound version of a suppression handler that must not be reused. */ interface Bound { /** * Invoked to prepare the suppression handler, i.e. to write an exception handler entry if appropriate. * * @param methodVisitor The method visitor to apply the preparation to. */ void onPrepare(MethodVisitor methodVisitor); /** * Invoked at the start of a method. * * @param methodVisitor The method visitor of the instrumented method. */ void onStart(MethodVisitor methodVisitor); /** * Invoked at the end of a method. * * @param methodVisitor The method visitor of the instrumented method. * @param implementationContext The implementation context to use. * @param methodSizeHandler The advice method's method size handler. * @param stackMapFrameHandler A handler for translating and injecting stack map frames. * @param returnType The return type of the advice method. */ void onEnd(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType); /** * Invoked at the end of a method if the exception handler should be wrapped in a skipping block. * * @param methodVisitor The method visitor of the instrumented method. * @param implementationContext The implementation context to use. * @param methodSizeHandler The advice method's method size handler. * @param stackMapFrameHandler A handler for translating and injecting stack map frames. * @param returnType The return type of the advice method. */ void onEndWithSkip(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType); } /** * A non-operational suppression handler that does not suppress any method. */ enum NoOp implements SuppressionHandler, Bound { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public Bound bind(StackManipulation exceptionHandler) { return this; } /** * {@inheritDoc} */ public void onPrepare(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void onStart(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void onEnd(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType) { /* do nothing */ } /** * {@inheritDoc} */ public void onEndWithSkip(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType) { /* do nothing */ } } /** * A suppression handler that suppresses a given throwable type. */ @HashCodeAndEqualsPlugin.Enhance class Suppressing implements SuppressionHandler { /** * The suppressed throwable type. */ private final TypeDescription suppressedType; /** * Creates a new suppressing suppression handler. * * @param suppressedType The suppressed throwable type. */ protected Suppressing(TypeDescription suppressedType) { this.suppressedType = suppressedType; } /** * Resolves an appropriate suppression handler. * * @param suppressedType The suppressed type or {@link NoExceptionHandler} if no type should be suppressed. * @return An appropriate suppression handler. */ protected static SuppressionHandler of(TypeDescription suppressedType) { return isNoExceptionHandler(suppressedType) ? NoOp.INSTANCE : new Suppressing(suppressedType); } /** * {@inheritDoc} */ public SuppressionHandler.Bound bind(StackManipulation exceptionHandler) { return new Bound(suppressedType, exceptionHandler); } /** * An active, bound suppression handler. */ protected static class Bound implements SuppressionHandler.Bound { /** * The suppressed throwable type. */ private final TypeDescription suppressedType; /** * The stack manipulation to apply within a suppression handler. */ private final StackManipulation exceptionHandler; /** * A label indicating the start of the method. */ private final Label startOfMethod; /** * A label indicating the end of the method. */ private final Label endOfMethod; /** * Creates a new active, bound suppression handler. * * @param suppressedType The suppressed throwable type. * @param exceptionHandler The stack manipulation to apply within a suppression handler. */ protected Bound(TypeDescription suppressedType, StackManipulation exceptionHandler) { this.suppressedType = suppressedType; this.exceptionHandler = exceptionHandler; startOfMethod = new Label(); endOfMethod = new Label(); } /** * {@inheritDoc} */ public void onPrepare(MethodVisitor methodVisitor) { methodVisitor.visitTryCatchBlock(startOfMethod, endOfMethod, endOfMethod, suppressedType.getInternalName()); } /** * {@inheritDoc} */ public void onStart(MethodVisitor methodVisitor) { methodVisitor.visitLabel(startOfMethod); } /** * {@inheritDoc} */ public void onEnd(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType) { methodVisitor.visitLabel(endOfMethod); stackMapFrameHandler.injectExceptionFrame(methodVisitor); methodSizeHandler.requireStackSize(1 + exceptionHandler.apply(methodVisitor, implementationContext).getMaximalSize()); if (returnType.represents(boolean.class) || returnType.represents(byte.class) || returnType.represents(short.class) || returnType.represents(char.class) || returnType.represents(int.class)) { methodVisitor.visitInsn(Opcodes.ICONST_0); } else if (returnType.represents(long.class)) { methodVisitor.visitInsn(Opcodes.LCONST_0); } else if (returnType.represents(float.class)) { methodVisitor.visitInsn(Opcodes.FCONST_0); } else if (returnType.represents(double.class)) { methodVisitor.visitInsn(Opcodes.DCONST_0); } else if (!returnType.represents(void.class)) { methodVisitor.visitInsn(Opcodes.ACONST_NULL); } } /** * {@inheritDoc} */ public void onEndWithSkip(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType) { Label skipExceptionHandler = new Label(); methodVisitor.visitJumpInsn(Opcodes.GOTO, skipExceptionHandler); onEnd(methodVisitor, implementationContext, methodSizeHandler, stackMapFrameHandler, returnType); methodVisitor.visitLabel(skipExceptionHandler); stackMapFrameHandler.injectReturnFrame(methodVisitor); } } } } /** * A relocation handler is responsible for chaining the usual control flow of an instrumented method. */ interface RelocationHandler { /** * Binds this relocation handler to a relocation dispatcher. * * @param instrumentedMethod The instrumented method. * @param relocation The relocation to apply. * @return A bound relocation handler. */ Bound bind(MethodDescription instrumentedMethod, Relocation relocation); /** * A re-locator is responsible for triggering a relocation if a relocation handler triggers a relocating condition. */ interface Relocation { /** * Applies this relocator. * * @param methodVisitor The method visitor to use. */ void apply(MethodVisitor methodVisitor); /** * A relocation that unconditionally jumps to a given label. */ @HashCodeAndEqualsPlugin.Enhance class ForLabel implements Relocation { /** * The label to jump to. */ private final Label label; /** * Creates a new relocation for an unconditional jump to a given label. * * @param label The label to jump to. */ public ForLabel(Label label) { this.label = label; } /** * {@inheritDoc} */ public void apply(MethodVisitor methodVisitor) { methodVisitor.visitJumpInsn(Opcodes.GOTO, label); } } } /** * A bound {@link RelocationHandler}. */ interface Bound { /** * Indicates that this relocation handler does not require a minimal stack size. */ int NO_REQUIRED_SIZE = 0; /** * Applies this relocation handler. * * @param methodVisitor The method visitor to use. * @param offset The offset of the relevant value. * @return The minimal required stack size to apply this relocation handler. */ int apply(MethodVisitor methodVisitor, int offset); } /** * A disabled relocation handler that does never trigger a relocation. */ enum Disabled implements RelocationHandler, Bound { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public Bound bind(MethodDescription instrumentedMethod, Relocation relocation) { return this; } /** * {@inheritDoc} */ public int apply(MethodVisitor methodVisitor, int offset) { return NO_REQUIRED_SIZE; } } /** * A relocation handler that triggers a relocation for a default or non-default value. */ enum ForValue implements RelocationHandler { /** * A relocation handler for an {@code int} type or any compatible type. */ INTEGER(Opcodes.ILOAD, Opcodes.IFNE, Opcodes.IFEQ, 0) { @Override protected void convertValue(MethodVisitor methodVisitor) { /* do nothing */ } }, /** * A relocation handler for a {@code long} type. */ LONG(Opcodes.LLOAD, Opcodes.IFNE, Opcodes.IFEQ, 0) { @Override protected void convertValue(MethodVisitor methodVisitor) { methodVisitor.visitInsn(Opcodes.L2I); } }, /** * A relocation handler for a {@code float} type. */ FLOAT(Opcodes.FLOAD, Opcodes.IFNE, Opcodes.IFEQ, 2) { @Override protected void convertValue(MethodVisitor methodVisitor) { methodVisitor.visitInsn(Opcodes.FCONST_0); methodVisitor.visitInsn(Opcodes.FCMPL); } }, /** * A relocation handler for a {@code double} type. */ DOUBLE(Opcodes.DLOAD, Opcodes.IFNE, Opcodes.IFEQ, 4) { @Override protected void convertValue(MethodVisitor methodVisitor) { methodVisitor.visitInsn(Opcodes.DCONST_0); methodVisitor.visitInsn(Opcodes.DCMPL); } }, /** * A relocation handler for a reference type. */ REFERENCE(Opcodes.ALOAD, Opcodes.IFNONNULL, Opcodes.IFNULL, 0) { @Override protected void convertValue(MethodVisitor methodVisitor) { /* do nothing */ } }; /** * An opcode for loading a value of the represented type from the local variable array. */ private final int load; /** * The opcode to check for a non-default value. */ private final int defaultJump; /** * The opcode to check for a default value. */ private final int nonDefaultJump; /** * The minimal required stack size to apply this relocation handler. */ private final int requiredSize; /** * Creates a new relocation handler for a type's default or non-default value. * * @param load An opcode for loading a value of the represented type from the local variable array. * @param defaultJump The opcode to check for a non-default value. * @param nonDefaultJump The opcode to check for a default value. * @param requiredSize The minimal required stack size to apply this relocation handler. */ ForValue(int load, int defaultJump, int nonDefaultJump, int requiredSize) { this.load = load; this.defaultJump = defaultJump; this.nonDefaultJump = nonDefaultJump; this.requiredSize = requiredSize; } /** * Resolves a relocation handler for a given type. * * @param typeDefinition The type to be resolved for a relocation attempt. * @param inverted {@code true} if the relocation should be applied for any non-default value of a type. * @return An appropriate relocation handler. */ protected static RelocationHandler of(TypeDefinition typeDefinition, boolean inverted) { ForValue skipDispatcher; if (typeDefinition.represents(long.class)) { skipDispatcher = LONG; } else if (typeDefinition.represents(float.class)) { skipDispatcher = FLOAT; } else if (typeDefinition.represents(double.class)) { skipDispatcher = DOUBLE; } else if (typeDefinition.represents(void.class)) { throw new IllegalStateException("Cannot skip on default value for void return type"); } else if (typeDefinition.isPrimitive()) { // anyOf(byte, short, char, int) skipDispatcher = INTEGER; } else { skipDispatcher = REFERENCE; } return inverted ? skipDispatcher.new Inverted() : skipDispatcher; } /** * Applies a value conversion prior to a applying a conditional jump. * * @param methodVisitor The method visitor to use. */ protected abstract void convertValue(MethodVisitor methodVisitor); /** * {@inheritDoc} */ public RelocationHandler.Bound bind(MethodDescription instrumentedMethod, Relocation relocation) { return new Bound(instrumentedMethod, relocation, false); } /** * An inverted version of the outer relocation handler. */ @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true) protected class Inverted implements RelocationHandler { /** * {@inheritDoc} */ public Bound bind(MethodDescription instrumentedMethod, Relocation relocation) { return new ForValue.Bound(instrumentedMethod, relocation, true); } } /** * A bound relocation handler for {@link ForValue}. */ @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true) protected class Bound implements RelocationHandler.Bound { /** * The instrumented method. */ private final MethodDescription instrumentedMethod; /** * The relocation to apply. */ private final Relocation relocation; /** * {@code true} if the relocation should be applied for any non-default value of a type. */ private final boolean inverted; /** * Creates a new bound relocation handler. * * @param instrumentedMethod The instrumented method. * @param relocation The relocation to apply. * @param inverted {@code true} if the relocation should be applied for any non-default value of a type. */ protected Bound(MethodDescription instrumentedMethod, Relocation relocation, boolean inverted) { this.instrumentedMethod = instrumentedMethod; this.relocation = relocation; this.inverted = inverted; } /** * {@inheritDoc} */ public int apply(MethodVisitor methodVisitor, int offset) { if (instrumentedMethod.isConstructor()) { throw new IllegalStateException("Cannot skip code execution from constructor: " + instrumentedMethod); } methodVisitor.visitVarInsn(load, offset); convertValue(methodVisitor); Label noSkip = new Label(); methodVisitor.visitJumpInsn(inverted ? nonDefaultJump : defaultJump, noSkip); relocation.apply(methodVisitor); methodVisitor.visitLabel(noSkip); return requiredSize; } } } /** * A relocation handler that is triggered if the checked value is an instance of a given type. */ @HashCodeAndEqualsPlugin.Enhance class ForType implements RelocationHandler { /** * The type that triggers a relocation. */ private final TypeDescription typeDescription; /** * Creates a new relocation handler that triggers a relocation if a value is an instance of a given type. * * @param typeDescription The type that triggers a relocation. */ protected ForType(TypeDescription typeDescription) { this.typeDescription = typeDescription; } /** * Resolves a relocation handler that is triggered if the checked instance is of a given type. * * @param typeDescription The type that triggers a relocation. * @param checkedType The type that is carrying the checked value. * @return An appropriate relocation handler. */ protected static RelocationHandler of(TypeDescription typeDescription, TypeDefinition checkedType) { if (typeDescription.represents(void.class)) { return Disabled.INSTANCE; } else if (typeDescription.represents(OnDefaultValue.class)) { return ForValue.of(checkedType, false); } else if (typeDescription.represents(OnNonDefaultValue.class)) { return ForValue.of(checkedType, true); } else if (typeDescription.isPrimitive() || checkedType.isPrimitive()) { throw new IllegalStateException("Cannot skip method by instance type for primitive return type " + checkedType); } else { return new ForType(typeDescription); } } /** * {@inheritDoc} */ public RelocationHandler.Bound bind(MethodDescription instrumentedMethod, Relocation relocation) { return new Bound(instrumentedMethod, relocation); } /** * A bound relocation handler for {@link ForType}. */ @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true) protected class Bound implements RelocationHandler.Bound { /** * The instrumented method. */ private final MethodDescription instrumentedMethod; /** * The relocation to use. */ private final Relocation relocation; /** * Creates a new bound relocation handler. * * @param instrumentedMethod The instrumented method. * @param relocation The relocation to apply. */ protected Bound(MethodDescription instrumentedMethod, Relocation relocation) { this.instrumentedMethod = instrumentedMethod; this.relocation = relocation; } /** * {@inheritDoc} */ public int apply(MethodVisitor methodVisitor, int offset) { if (instrumentedMethod.isConstructor()) { throw new IllegalStateException("Cannot skip code execution from constructor: " + instrumentedMethod); } methodVisitor.visitVarInsn(Opcodes.ALOAD, offset); methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, typeDescription.getInternalName()); Label noSkip = new Label(); methodVisitor.visitJumpInsn(Opcodes.IFEQ, noSkip); relocation.apply(methodVisitor); methodVisitor.visitLabel(noSkip); return NO_REQUIRED_SIZE; } } } } /** * Represents a resolved dispatcher. */ interface Resolved extends Dispatcher { /** * Returns the named types defined by this advice. * * @return The named types defined by this advice. */ Map getNamedTypes(); /** * Binds this dispatcher for resolution to a specific method. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param methodVisitor The method visitor for writing the instrumented method. * @param implementationContext The implementation context to use. * @param assigner The assigner to use. * @param argumentHandler A handler for accessing values on the local variable array. * @param methodSizeHandler A handler for computing the method size requirements. * @param stackMapFrameHandler A handler for translating and injecting stack map frames. * @param exceptionHandler The stack manipulation to apply within a suppression handler. * @param relocation A relocation to use with a relocation handler. * @return A dispatcher that is bound to the instrumented method. */ Bound bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, ArgumentHandler.ForInstrumentedMethod argumentHandler, MethodSizeHandler.ForInstrumentedMethod methodSizeHandler, StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, StackManipulation exceptionHandler, RelocationHandler.Relocation relocation); Map getOffsetMapping(); /** * Represents a resolved dispatcher for entering a method. */ interface ForMethodEnter extends Resolved { /** * Returns {@code true} if the first discovered line number information should be prepended to the advice code. * * @return {@code true} if the first discovered line number information should be prepended to the advice code. */ boolean isPrependLineNumber(); /** * Returns the actual advice type, even if it is not required post advice processing. * * @return The actual advice type, even if it is not required post advice processing. */ TypeDefinition getActualAdviceType(); } /** * Represents a resolved dispatcher for exiting a method. */ interface ForMethodExit extends Resolved { /** * Returns the type of throwable for which this exit advice is supposed to be invoked. * * @return The {@link Throwable} type for which to invoke this exit advice or a description of {@link NoExceptionHandler} * if this exit advice does not expect to be invoked upon any throwable. */ TypeDescription getThrowable(); /** * Returns a factory for creating an {@link ArgumentHandler}. * * @return A factory for creating an {@link ArgumentHandler}. */ ArgumentHandler.Factory getArgumentHandlerFactory(); } /** * An abstract base implementation of a {@link Resolved} dispatcher. */ @HashCodeAndEqualsPlugin.Enhance abstract class AbstractBase implements Resolved { /** * The represented advice method. */ protected final MethodDescription.InDefinedShape adviceMethod; /** * The post processor to apply. */ protected final PostProcessor postProcessor; /** * A mapping from offset to a mapping for this offset with retained iteration order of the method's parameters. */ protected final Map offsetMappings; /** * The suppression handler to use. */ protected final SuppressionHandler suppressionHandler; /** * The relocation handler to use. */ protected final RelocationHandler relocationHandler; /** * Creates a new resolved version of a dispatcher. * * @param adviceMethod The represented advice method. * @param postProcessor The post processor to use. * @param factories A list of factories to resolve for the parameters of the advice method. * @param throwableType The type to handle by a suppression handler or {@link NoExceptionHandler} to not handle any exceptions. * @param relocatableType The type to trigger a relocation of the method's control flow or {@code void} if no relocation should be executed. * @param adviceType The applied advice type. */ protected AbstractBase(MethodDescription.InDefinedShape adviceMethod, PostProcessor postProcessor, List> factories, TypeDescription throwableType, TypeDescription relocatableType, OffsetMapping.Factory.AdviceType adviceType) { this.adviceMethod = adviceMethod; this.postProcessor = postProcessor; Map> offsetMappings = new HashMap<>(); for (OffsetMapping.Factory factory : factories) { offsetMappings.put(TypeDescription.ForLoadedType.of(factory.getAnnotationType()), factory); } this.offsetMappings = new LinkedHashMap<>(); for (ParameterDescription.InDefinedShape parameterDescription : adviceMethod.getParameters()) { OffsetMapping offsetMapping = null; for (AnnotationDescription annotationDescription : parameterDescription.getDeclaredAnnotations()) { OffsetMapping.Factory factory = offsetMappings.get(annotationDescription.getAnnotationType()); if (factory != null) { @SuppressWarnings("unchecked, rawtypes") OffsetMapping current = factory.make(parameterDescription, (AnnotationDescription.Loadable) annotationDescription.prepare(factory.getAnnotationType()), adviceType); if (offsetMapping == null) { offsetMapping = current; } else { throw new IllegalStateException(parameterDescription + " is bound to both " + current + " and " + offsetMapping); } } } this.offsetMappings.put(parameterDescription.getOffset(), offsetMapping == null ? new OffsetMapping.ForArgument.Unresolved(parameterDescription) : offsetMapping); } suppressionHandler = SuppressionHandler.Suppressing.of(throwableType); relocationHandler = RelocationHandler.ForType.of(relocatableType, adviceMethod.getReturnType()); } /** * {@inheritDoc} */ public boolean isAlive() { return true; } @Override public Map getOffsetMapping() { return this.offsetMappings; } } } /** * A bound resolution of an advice method. */ interface Bound { /** * Prepares the advice method's exception handlers. */ void prepare(); /** * Initialized the advice's methods local variables. */ void initialize(); /** * Applies this dispatcher. */ void apply(); } /** * An implementation for inactive devise that does not write any byte code. */ enum Inactive implements Dispatcher.Unresolved, Resolved.ForMethodEnter, Resolved.ForMethodExit, Bound { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public boolean isAlive() { return false; } /** * {@inheritDoc} */ public boolean isBinary() { return false; } /** * {@inheritDoc} */ public TypeDescription getAdviceType() { return TypeDescription.VOID; } /** * {@inheritDoc} */ public boolean isPrependLineNumber() { return false; } /** * {@inheritDoc} */ public TypeDefinition getActualAdviceType() { return TypeDescription.VOID; } /** * {@inheritDoc} */ public Map getNamedTypes() { return Collections.emptyMap(); } /** * {@inheritDoc} */ public TypeDescription getThrowable() { return NoExceptionHandler.DESCRIPTION; } /** * {@inheritDoc} */ public ArgumentHandler.Factory getArgumentHandlerFactory() { return ArgumentHandler.Factory.SIMPLE; } /** * {@inheritDoc} */ public Resolved.ForMethodEnter asMethodEnter(List> userFactories, ClassReader classReader, Unresolved methodExit, PostProcessor.Factory postProcessorFactory) { return this; } /** * {@inheritDoc} */ public Resolved.ForMethodExit asMethodExit(List> userFactories, ClassReader classReader, Unresolved methodEnter, PostProcessor.Factory postProcessorFactory) { return this; } /** * {@inheritDoc} */ public void prepare() { /* do nothing */ } /** * {@inheritDoc} */ public void initialize() { /* do nothing */ } /** * {@inheritDoc} */ public void apply() { /* do nothing */ } /** * {@inheritDoc} */ public Bound bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, ArgumentHandler.ForInstrumentedMethod argumentHandler, MethodSizeHandler.ForInstrumentedMethod methodSizeHandler, StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, StackManipulation exceptionHandler, RelocationHandler.Relocation relocation) { return this; } @Override public Map getOffsetMapping() { return null; } } /** * A dispatcher for an advice method that is being inlined into the instrumented method. */ @HashCodeAndEqualsPlugin.Enhance class Inlining implements Unresolved { /** * The advice method. */ protected final MethodDescription.InDefinedShape adviceMethod; /** * A mapping of all available local variables by their name to their type. */ private final Map namedTypes; /** * Creates a dispatcher for inlined advice method. * * @param adviceMethod The advice method. */ protected Inlining(MethodDescription.InDefinedShape adviceMethod) { this.adviceMethod = adviceMethod; namedTypes = new HashMap<>(); for (ParameterDescription parameterDescription : adviceMethod.getParameters().filter(isAnnotatedWith(Local.class))) { String name = parameterDescription.getDeclaredAnnotations() .ofType(Local.class).getValue(OffsetMapping.ForLocalValue.Factory.LOCAL_VALUE) .resolve(String.class); TypeDefinition previous = namedTypes.put(name, parameterDescription.getType()); if (previous != null && !previous.equals(parameterDescription.getType())) { throw new IllegalStateException("Local variable for " + name + " is defined with inconsistent types"); } } } /** * {@inheritDoc} */ public boolean isAlive() { return true; } /** * {@inheritDoc} */ public boolean isBinary() { return true; } /** * {@inheritDoc} */ public TypeDescription getAdviceType() { return adviceMethod.getReturnType().asErasure(); } /** * {@inheritDoc} */ public Map getNamedTypes() { return namedTypes; } /** * {@inheritDoc} */ public Dispatcher.Resolved.ForMethodEnter asMethodEnter(List> userFactories, ClassReader classReader, Unresolved methodExit, PostProcessor.Factory postProcessorFactory) { return Resolved.ForMethodEnter.of(adviceMethod, postProcessorFactory.make(adviceMethod, false), namedTypes, userFactories, methodExit.getAdviceType(), classReader, methodExit.isAlive()); } /** * {@inheritDoc} */ public Dispatcher.Resolved.ForMethodExit asMethodExit(List> userFactories, ClassReader classReader, Unresolved methodEnter, PostProcessor.Factory postProcessorFactory) { Map namedTypes = new HashMap<>(methodEnter.getNamedTypes()), uninitializedNamedTypes = new HashMap<>(); for (Map.Entry entry : this.namedTypes.entrySet()) { TypeDefinition typeDefinition = namedTypes.get(entry.getKey()), uninitializedTypeDefinition = uninitializedNamedTypes.get(entry.getKey()); if (typeDefinition == null && uninitializedTypeDefinition == null) { namedTypes.put(entry.getKey(), entry.getValue()); uninitializedNamedTypes.put(entry.getKey(), entry.getValue()); } else if (!(typeDefinition == null ? uninitializedTypeDefinition : typeDefinition).equals(entry.getValue())) { throw new IllegalStateException("Local variable for " + entry.getKey() + " is defined with inconsistent types"); } } return Resolved.ForMethodExit.of(adviceMethod, postProcessorFactory.make(adviceMethod, true), namedTypes, uninitializedNamedTypes, userFactories, classReader, methodEnter.getAdviceType()); } public Dispatcher.Resolved.ForMethodExit asMethodExitNonThrowable( List> userFactories, ClassReader classReader, Unresolved methodEnter, PostProcessor.Factory postProcessorFactory) { Map namedTypes = new HashMap<>(methodEnter.getNamedTypes()), uninitializedNamedTypes = new HashMap<>(); for (Map.Entry entry : this.namedTypes.entrySet()) { TypeDefinition typeDefinition = namedTypes.get(entry.getKey()), uninitializedTypeDefinition = uninitializedNamedTypes.get(entry.getKey()); if (typeDefinition == null && uninitializedTypeDefinition == null) { namedTypes.put(entry.getKey(), entry.getValue()); uninitializedNamedTypes.put(entry.getKey(), entry.getValue()); } else if (!(typeDefinition == null ? uninitializedTypeDefinition : typeDefinition).equals(entry.getValue())) { throw new IllegalStateException("Local variable for " + entry.getKey() + " is defined with inconsistent types"); } } return Resolved.ForMethodExit.ofNonThrowable(adviceMethod, postProcessorFactory.make(adviceMethod, true), namedTypes, uninitializedNamedTypes, userFactories, classReader, methodEnter.getAdviceType()); } /** * A resolved version of a dispatcher. */ protected abstract static class Resolved extends Dispatcher.Resolved.AbstractBase { /** * A class reader to query for the class file of the advice method. */ protected final ClassReader classReader; /** * Creates a new resolved version of a dispatcher. * * @param adviceMethod The represented advice method. * @param postProcessor The post processor to apply. * @param factories A list of factories to resolve for the parameters of the advice method. * @param throwableType The type to handle by a suppression handler or {@link NoExceptionHandler} to not handle any exceptions. * @param relocatableType The type to trigger a relocation of the method's control flow or {@code void} if no relocation should be executed. * @param classReader A class reader to query for the class file of the advice method. */ protected Resolved(MethodDescription.InDefinedShape adviceMethod, PostProcessor postProcessor, List> factories, TypeDescription throwableType, TypeDescription relocatableType, ClassReader classReader) { super(adviceMethod, postProcessor, factories, throwableType, relocatableType, OffsetMapping.Factory.AdviceType.INLINING); this.classReader = classReader; } /** * Resolves the initialization types of this advice method. * * @param argumentHandler The argument handler to use for resolving the initialization. * @return A mapping of parameter offsets to the type to initialize. */ protected abstract Map resolveInitializationTypes(ArgumentHandler argumentHandler); /** * Applies a resolution for a given instrumented method. * * @param methodVisitor A method visitor for writing byte code to the instrumented method. * @param implementationContext The implementation context to use. * @param assigner The assigner to use. * @param argumentHandler A handler for accessing values on the local variable array. * @param methodSizeHandler A handler for computing the method size requirements. * @param stackMapFrameHandler A handler for translating and injecting stack map frames. * @param instrumentedType A description of the instrumented type. * @param instrumentedMethod A description of the instrumented method. * @param suppressionHandler A bound suppression handler that is used for suppressing exceptions of this advice method. * @param relocationHandler A bound relocation handler that is responsible for considering a non-standard control flow. * @param exceptionHandler The exception handler that is resolved for the instrumented method. * @return A method visitor for visiting the advice method's byte code. */ protected abstract MethodVisitor apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, ArgumentHandler.ForInstrumentedMethod argumentHandler, MethodSizeHandler.ForInstrumentedMethod methodSizeHandler, StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, TypeDescription instrumentedType, MethodDescription instrumentedMethod, SuppressionHandler.Bound suppressionHandler, RelocationHandler.Bound relocationHandler, StackManipulation exceptionHandler); /** * A bound advice method that copies the code by first extracting the exception table and later appending the * code of the method without copying any meta data. */ protected class AdviceMethodInliner extends ClassVisitor implements Bound { /** * A description of the instrumented type. */ protected final TypeDescription instrumentedType; /** * The instrumented method. */ protected final MethodDescription instrumentedMethod; /** * The method visitor for writing the instrumented method. */ protected final MethodVisitor methodVisitor; /** * The implementation context to use. */ protected final Implementation.Context implementationContext; /** * The assigner to use. */ protected final Assigner assigner; /** * A handler for accessing values on the local variable array. */ protected final ArgumentHandler.ForInstrumentedMethod argumentHandler; /** * A handler for computing the method size requirements. */ protected final MethodSizeHandler.ForInstrumentedMethod methodSizeHandler; /** * A handler for translating and injecting stack map frames. */ protected final StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler; /** * A bound suppression handler that is used for suppressing exceptions of this advice method. */ protected final SuppressionHandler.Bound suppressionHandler; /** * A bound relocation handler that is responsible for considering a non-standard control flow. */ protected final RelocationHandler.Bound relocationHandler; /** * The exception handler that is resolved for the instrumented method. */ protected final StackManipulation exceptionHandler; /** * A class reader for parsing the class file containing the represented advice method. */ protected final ClassReader classReader; /** * The labels that were found during parsing the method's exception handler in the order of their discovery. */ protected final List