From 4afed6b12f21ccd21278b63448950642018cf052 Mon Sep 17 00:00:00 2001 From: Njabulo Date: Fri, 17 Jan 2025 18:09:13 +0200 Subject: [PATCH] BAEL-8958-Introduction-to-New-Relic-for-Java --- libraries-apm/README.md | 1 + .../currency-converter/newrelic/newrelic.yml | 596 ++++++++++++++++++ .../new-relic/currency-converter/pom.xml | 78 +++ .../CurrencyConverterApplication.java | 12 + .../currency_converter/config/AppConfig.java | 13 + .../config/RedisConfig.java | 36 ++ .../CurrencyConverterController.java | 24 + .../dto/ExchangeRateResponse.java | 62 ++ .../service/CurrencyConverterService.java | 114 ++++ .../src/main/resources/application.properties | 15 + libraries-apm/new-relic/pom.xml | 21 + libraries-apm/pom.xml | 20 + 12 files changed, 992 insertions(+) create mode 100644 libraries-apm/README.md create mode 100644 libraries-apm/new-relic/currency-converter/newrelic/newrelic.yml create mode 100644 libraries-apm/new-relic/currency-converter/pom.xml create mode 100644 libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/CurrencyConverterApplication.java create mode 100644 libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/config/AppConfig.java create mode 100644 libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/config/RedisConfig.java create mode 100644 libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/controller/CurrencyConverterController.java create mode 100644 libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/dto/ExchangeRateResponse.java create mode 100644 libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/service/CurrencyConverterService.java create mode 100644 libraries-apm/new-relic/currency-converter/src/main/resources/application.properties create mode 100644 libraries-apm/new-relic/pom.xml create mode 100644 libraries-apm/pom.xml diff --git a/libraries-apm/README.md b/libraries-apm/README.md new file mode 100644 index 000000000000..5616cce48b45 --- /dev/null +++ b/libraries-apm/README.md @@ -0,0 +1 @@ +## Relevant Articles diff --git a/libraries-apm/new-relic/currency-converter/newrelic/newrelic.yml b/libraries-apm/new-relic/currency-converter/newrelic/newrelic.yml new file mode 100644 index 000000000000..7a972e556094 --- /dev/null +++ b/libraries-apm/new-relic/currency-converter/newrelic/newrelic.yml @@ -0,0 +1,596 @@ +# This file configures the New Relic agent. New Relic monitors +# Java applications with deep visibility and low overhead. For more details and additional +# configuration options visit https://docs.newrelic.com/docs/agents/java-agent/configuration/java-agent-configuration-config-file. +# +# <%= generated_for_user %> +# +# This section is for settings common to all environments. +# Do not add anything above this next line. +common: &default_settings + + # ============================== LICENSE KEY =============================== + # You must specify the license key associated with your New Relic + # account. For example, if your license key is 12345 use this: + # license_key: '12345' + # The key binds your agent's data to your account in the New Relic UI. + license_key: 'REQUIRED_LICENSE_KEY' + + # Agent enabled + # Use this setting to disable the agent instead of removing it from the startup command. + # Default is true. + agent_enabled: true + + # Set the name of your application as you'd like it to show up in the New Relic UI. + # If enable_auto_app_naming is false, the agent reports all data to this application. + # Otherwise, the agent reports only background tasks (transactions for non-web applications) + # to this application. To report data to more than one application + # (useful for rollup reporting), separate the application names with ";". + # For example, to report data to "My Application" and "My Application 2" use this: + # app_name: My Application;My Application 2 + # This setting is required. Up to 3 different application names can be specified. + # The first application name must be unique. + app_name: Currency Converter + + # To enable high security, set this property to true. When in high + # security mode, the agent will use SSL and obfuscated SQL. Additionally, + # request parameters and message parameters will not be sent to New Relic. + high_security: false + + # Set to true to enable support for auto app naming. + # The name of each web app is detected automatically + # and the agent reports data separately for each one. + # This provides a finer-grained performance breakdown for + # web apps in New Relic. + # Default is false. + enable_auto_app_naming: false + + # Set to true to enable component-based transaction naming. + # Set to false to use the URI of a web request as the name of the transaction. + # Default is true. + enable_auto_transaction_naming: true + + # The agent uses its own log file to keep its logging + # separate from that of your application. Specify the log level here. + # This setting is dynamic, so changes do not require restarting your application. + # The levels in increasing order of verboseness are: + # off, severe, warning, info, fine, finer, finest + # Default is info. + log_level: info + + # Log all data sent to and from New Relic in plain text. + # This setting is dynamic, so changes do not require restarting your application. + # Default is false. + audit_mode: false + + # The number of backup log files to save. + # Default is 1. + log_file_count: 1 + + # The maximum number of kbytes to write to any one log file. + # The log_file_count must be set greater than 1. + # Default is 0 (no limit). + log_limit_in_kbytes: 0 + + # Override other log rolling configuration and roll the logs daily. + # Default is false. + log_daily: false + + # The name of the log file. + # Default is newrelic_agent.log. + log_file_name: newrelic_agent.log + + # The log file directory. + # Default is the logs directory in the newrelic.jar parent directory. + #log_file_path: + + # AI Monitoring captures insights on the performance, quality, and cost of interactions with LLM models made with instrumented SDKs. + ai_monitoring: + + # Provides control over all AI Monitoring functionality. Set as true to enable all AI Monitoring features. + # Default is false. + enabled: false + + # Provides control over whether attributes for the input and output content should be added to LLM events. + record_content: + + # Set as false to disable attributes for the input and output content. + # Default is true. + enabled: true + + # Provides the ability to forward application logs to New Relic, generate log usage metrics, + # and decorate local application log files with agent metadata for use with third party log forwarders. + # The application_logging.forwarding and application_logging.local_decorating should not be used together. + application_logging: + + # Provides control over all the application logging features for forwarding, local log + # decorating, and metrics features. Set as false to disable all application logging features. + # Default is true. + enabled: true + + # The agent will automatically forward application logs to New Relic in + # a format that includes agent metadata for linking them to traces and errors. + forwarding: + + # When true, application logs will be forwarded to New Relic. The default is true. + enabled: true + + # Application log events are collected up to the configured amount. Afterwards, + # events are sampled to maintain an even distribution across the harvest cycle. + # Default is 10000. Setting to 0 will disable. + #max_samples_stored: 10000 + + # Whether the log events should include context from loggers with support for that. + context_data: + + # When true, application logs will contain context data. + enabled: false + + # A comma separated list of attribute keys whose values should be sent to New Relic. + #include: + + # A comma separated list of attribute keys whose values should not be sent to New Relic. + #exclude: + + # The agent will generate metrics to indicate the number of + # application log events occurring at each distinct log level. + metrics: + + # When true, application log metrics will be reported. The default is true. + enabled: true + + # The agent will add linking metadata to each log line in your application log files. + # This feature should only be used if you want to use a third party log forwarder, instead + # of the agent's built-in forwarding feature, to send your application log events to New Relic. + #local_decorating: + + # When true, the agent will decorate your application log files with linking metadata. The default is false. + #enabled: false + + # Adds integration with CodeStream, introducing Code-Level Metrics! Golden Signals visible in your + # IDE through New Relic CodeStream. + code_level_metrics: + + # When true the agent will capture namespace and function information + # on spans to enable code level metrics in CodeStream. + enabled: true + + # Proxy settings for connecting to the New Relic server: + # If a proxy is used, the host setting is required. Other settings + # are optional. Default port is 8080. The username and password + # settings will be used to authenticate to Basic Auth challenges + # from a proxy server. Proxy scheme will allow the agent to + # connect through proxies using the HTTPS scheme. + #proxy_host: hostname + #proxy_port: 8080 + #proxy_user: username + #proxy_password: password + #proxy_scheme: https + + # Limits the number of lines to capture for each stack trace. + # Default is 30 + max_stack_trace_lines: 30 + + # Provides the ability to configure the attributes sent to New Relic. These + # attributes can be found in transaction traces, traced errors, + # transaction events, and page views. + attributes: + + # When true, attributes will be sent to New Relic. The default is true. + enabled: true + + #A comma separated list of attribute keys whose values should + # be sent to New Relic. + #include: + + # A comma separated list of attribute keys whose values should + # not be sent to New Relic. + #exclude: + + + # Defines which sets of http attributes the agent will send: standard, legacy or both (default). + # Having the agent send both sets will increase ingestion. + # Having the agent send only legacy may impact current or future functionality. + http_attribute_mode: both + + # Transaction tracer captures deep information about slow + # transactions and sends this to the New Relic service once a + # minute. Included in the transaction is the exact call sequence of + # the transactions including any SQL statements issued. + transaction_tracer: + + # Transaction tracer is enabled by default. Set this to false to turn it off. + # This feature is not available to Lite accounts and is automatically disabled. + # Default is true. + enabled: true + + # Threshold in seconds for when to collect a transaction + # trace. When the response time of a controller action exceeds + # this threshold, a transaction trace will be recorded and sent to + # New Relic. Valid values are any float value, or (default) "apdex_f", + # which will use the threshold for the "Frustrated" Apdex level + # (greater than four times the apdex_t value). + # Default is apdex_f. + transaction_threshold: apdex_f + + # When transaction tracer is on, SQL statements can optionally be + # recorded. The recorder has three modes, "off" which sends no + # SQL, "raw" which sends the SQL statement in its original form, + # and "obfuscated", which strips out numeric and string literals. + # Default is obfuscated. + record_sql: obfuscated + + # Set this to true to log SQL statements instead of recording them. + # SQL is logged using the record_sql mode. + # Default is false. + log_sql: false + + # Threshold in seconds for when to collect stack trace for a SQL + # call. In other words, when SQL statements exceed this threshold, + # then capture and send to New Relic the current stack trace. This is + # helpful for pinpointing where long SQL calls originate from. + # Default is 0.5 seconds. + stack_trace_threshold: 0.5 + + # Determines whether the agent will capture query plans for slow + # SQL queries. Only supported for MySQL and PostgreSQL. + # Default is true. + explain_enabled: true + + # Threshold for query execution time below which query plans will + # not be captured. Relevant only when `explain_enabled` is true. + # Default is 0.5 seconds. + explain_threshold: 0.5 + + # Use this setting to control the variety of transaction traces. + # The higher the setting, the greater the variety. + # Set this to 0 to always report the slowest transaction trace. + # Default is 20. + top_n: 20 + + # Error collector captures information about uncaught exceptions and + # sends them to New Relic for viewing. + error_collector: + + # This property enables the collection of errors. If the property is not + # set or the property is set to false, then errors will not be collected. + # Default is true. + enabled: true + + # Use this property to exclude specific exceptions from being reported as errors + # by providing a comma separated list of full class names. + # The default is to exclude akka.actor.ActorKilledException. If you want to override + # this, you must provide any new value as an empty list is ignored. + ignore_classes: + - "akka.actor.ActorKilledException" + + # Use this property to exclude specific http status codes from being reported as errors + # by providing a comma separated list of status codes. + # The default is to exclude 404s. If you want to override + # this, you must provide any new value because an empty list is ignored. + ignore_status_codes: 404 + + # Transaction events are used for histograms and percentiles. Non-aggregated data is collected + # for each web transaction and sent to the server on harvest. + transaction_events: + + # Set to false to disable transaction events. + # Default is true. + enabled: true + + # Events are collected up to the configured amount. Afterwards, events are sampled to + # maintain an even distribution across the harvest cycle. + # Default is 2000. Setting to 0 will disable. + max_samples_stored: 2000 + + # Distributed tracing lets you see the path that a request takes through your distributed system. + # This replaces the legacy Cross Application Tracing feature. + distributed_tracing: + + # Set to false to disable distributed tracing. + # Default is true. + enabled: true + + # Agent versions 5.10.0+ utilize both the newrelic header and W3C Trace Context headers for distributed tracing. + # The newrelic distributed tracing header allows interoperability with older agents that don't support W3C Trace Context headers. + # Agent versions that support W3C Trace Context headers will prioritize them over newrelic headers for distributed tracing. + # If you do not want to utilize the newrelic header, setting this to true will result in the agent excluding the newrelic header + # and only using W3C Trace Context headers for distributed tracing. + # Default is false. + exclude_newrelic_header: false + + # New Relic's distributed tracing UI uses Span events to display traces across different services. + # Span events capture attributes that describe execution context and provide linking metadata. + # Span events require distributed tracing to be enabled. + span_events: + + # Set to false to disable Span events. + # Default is true. + enabled: true + + # Determines the number of Span events that can be captured during an agent harvest cycle. + # Increasing the number of Span events can lead to additional agent overhead. A maximum value may be imposed server side by New Relic. + # Default is 2000 + max_samples_stored: 2000 + + # Provides the ability to filter the attributes attached to Span events. + # Custom attributes can be added to Span events using the NewRelic.getAgent().getTracedMethod().addCustomAttribute(...) API. + attributes: + + # When true, attributes will be sent to New Relic. The default is true. + enabled: true + + # A comma separated list of attribute keys whose values should be sent to New Relic. + #include: + + # A comma separated list of attribute keys whose values should not be sent to New Relic. + #exclude: + + # Thread profiler measures wall clock time, CPU time, and method call counts + # in your application's threads as they run. + # This feature is not available to Lite accounts and is automatically disabled. + thread_profiler: + + # Set to false to disable the thread profiler. + # Default is true. + enabled: true + + # New Relic Real User Monitoring (RUM) gives you insight into the performance real users are + # experiencing with your website. This is accomplished by measuring the time it takes for + # your users' browsers to download and render your web pages by injecting a small amount + # of JavaScript code into the header and footer of each page. + browser_monitoring: + + # By default the agent automatically inserts API calls in compiled JSPs to + # inject the monitoring JavaScript into web pages. Not all rendering engines are supported. + # See https://docs.newrelic.com/docs/agents/java-agent/instrumentation/new-relic-browser-java-agent#manual_instrumentation + # for instructions to add these manually to your pages. + # Set this attribute to false to turn off this behavior. + auto_instrument: true + + # For pages that emit the tag via a JSP tag library, enabling this setting + # will monitor JspWriter print/println calls for output of the element and + # inject the RUM script automatically. + tag_lib_instrument: false + + # If tag_lib_instrument is true, this is the regex pattern that will be used to detect HTML start head elements. Modify + # the regex if you have more complex start head elements (attributes for example). The defined pattern is case-insensitive. + tag_lib_head_pattern: '' + + # By default, the agent sends JVM input arguments to New Relic, where they are visible as Environment data. + # Set to false to disable the sending of JVM props. + send_jvm_props: true + + # Before sending JVM props to New Relic, the agent will obfuscate all data in a prop after an = sign. + # For example, the property -Dmy.prop=my-value will be sent to New Relic as -Dmy.prop=obfuscated. + # By default, the standard and extended JVM props (those beginning with -X*) are sent unobfuscated. + # Available since agent 8.16.0. + obfuscate_jvm_props: + #To disable this feature, and send all JVM props unobfuscated, set enabled to false. The default is true. + #enabled: true + + # A comma separated list of JVM property names whose values should be sent to New Relic unobfuscated. + #allow: + + # A comma separated list of JVM property names whose values should be sent to New Relic obfuscated. + #block: + + # Class transformer can be used to disable all agent instrumentation or specific instrumentation modules. + # All instrumentation modules can be found here: https://github.com/newrelic/newrelic-java-agent/tree/main/instrumentation + class_transformer: + + # This instrumentation reports the name of the user principal returned from + # HttpServletRequest.getUserPrincipal() when servlets and filters are invoked. + com.newrelic.instrumentation.servlet-user: + enabled: false + + com.newrelic.instrumentation.spring-aop-2: + enabled: false + + # This instrumentation reports metrics for resultset operations. + com.newrelic.instrumentation.jdbc-resultset: + enabled: false + + # Classes loaded by classloaders in this list will not be instrumented. + # This is a useful optimization for runtimes which use classloaders to + # load dynamic classes which the agent would not instrument. + classloader_excludes: + groovy.lang.GroovyClassLoader$InnerLoader, + org.codehaus.groovy.runtime.callsite.CallSiteClassLoader, + com.collaxa.cube.engine.deployment.BPELClassLoader, + org.springframework.data.convert.ClassGeneratingEntityInstantiator$ObjectInstantiatorClassGenerator, + org.mvel2.optimizers.impl.asm.ASMAccessorOptimizer$ContextClassLoader, + gw.internal.gosu.compiler.SingleServingGosuClassLoader, + + # Enhanced Spring transaction naming. + # This feature will name any transaction that originates from a Spring controller after + # the defined route and HTTP method. For example: "/customer/v1/edit (POST)". + # This includes controllers that implement or extend interfaces/classes with WebMVC related + # annotations (@RestController, @Controller, @RequestMapping, etc). By default, this is configured + # to false, which will name transactions for those types of controllers based on the controller + # class name and method. For example; "CustomerController/edit". This is the naming logic carried + # over from previous agent versions. "Standard" controllers, with all relevant annotations + # present on the actual class, will still get named based on route and HTTP method. + enhanced_spring_transaction_naming: false + + # By default, built-in actuator endpoints and custom actuator endpoints (using the @Endpoint annotation + # and it's subclasses) will all be named as "OperationHandler/handle" in New Relic. Setting this + # to true will result in the transaction name reflecting the actual base actuator endpoint URI. + # For example, invoking "/actuator/loggers" or "actuator/loggers/com.newrelic" will result in the + # transaction name "actuator/loggers (GET)". This is to prevent MGI. + # Default is false. + name_actuator_endpoints: false + + # Real-time profiling using Java Flight Recorder (JFR). + # This feature reports dimensional metrics to the ingest endpoint configured by + # metric_ingest_uri and events to the ingest endpoint configured by event_ingest_uri. + # Both ingest endpoints default to US production but they will be automatically set to EU + # production when using an EU license key. Other ingest endpoints can be configured manually. + # Requires a JVM that provides the JFR library. + jfr: + + # Set to true to enable Real-time profiling with JFR. + # Default is false. + enabled: false + + # Set to true to enable audit logging which will display all JFR metrics and events in each harvest batch. + # Audit logging is extremely verbose and should only be used for troubleshooting purposes. + # Default is false. + audit_logging: false + + # The time interval, in seconds, of how often JFR data is sent to New Relic. + # The default is 10 seconds. + harvest_interval: 10 + + # The size of the queue used to store JFR events. Increasing this can reduce gaps in JFR reported data + # but can also cause resource issues in the agent or cause data to be dropped if backend pipeline + # limits are exceeded. + # See: https://docs.newrelic.com/docs/data-apis/ingest-apis/event-api/introduction-event-api/#limits + # https://docs.newrelic.com/docs/data-apis/ingest-apis/metric-api/metric-api-limits-restricted-attributes/ + # Default is 250000 + queue_size: 250000 + + # User-configurable custom labels for this agent. Labels are name-value pairs. + # There is a maximum of 64 labels per agent. Names and values are limited to 255 characters. + # Names and values may not contain colons (:) or semicolons (;). + labels: + + # An example label + #label_name: label_value + + # New Relic Security vulnerability detection. + security: + # Determines whether the security data is sent to New Relic or not. When this is disabled and agent.enabled is + # true, the security module will run but data will not be sent. Default is false. + enabled: false + + # New Relic Security provides two modes: IAST and RASP + # Default is IAST. Due to the invasive nature of IAST scanning, DO NOT enable this mode in either a + # production environment or an environment where production data is processed. + mode: IAST + + # New Relic Security’s SaaS connection URL + validator_service_url: wss://csec.nr-data.net + + # To completely disable all security functionality, set this flag to false. This property is + # read only once at application start. Default is false. + agent: + enabled: false + + # This configuration allows users to specify a unique test identifier when running IAST Scan with CI/CD + iast_test_identifier: 'run-id' + + # Security controllers + scan_controllers: + # The scan_request_rate_limit configuration allows to specify maximum number of replay request played per minute. + # The maximum is 3600 and the minimum is 12 replay request per minute. + iast_scan_request_rate_limit: 3600 # Number of IAST replay request played per minute, Default is 3600 + # This configuration allows users to the number of application instances for a specific entity where IAST analysis is performed. + scan_instance_count: 0 # Values are 1 or 0, 0 signifies run on all application instances + + # The scan_schedule configuration allows to specify when IAST scans should be executed + scan_schedule: + # The delay field specifies the delay in minutes before the IAST scan starts. This allows to schedule the scan to start at a later time. + delay: 0 #In minutes, default is 0 min + # The duration field specifies the duration of the IAST scan in minutes. This determines how long the scan will run. + duration: 0 #In minutes, default is forever + + # The schedule field specifies a cron expression that defines when the IAST scan should start. + #schedule: "" #By default, schedule is inactive + + # Allow continuously sample collection of IAST events + always_sample_traces: false # Default is false + + # The exclude_from_iast_scan configuration allows to specify APIs, parameters, and categories that should not be scanned by Security Agents. + exclude_from_iast_scan: + # The api field specifies list of APIs using regular expression (regex) patterns that follow the syntax of Perl 5. The regex pattern should provide a complete match for the URL without the endpoint. + # Example: + # api: + # - .*account.* + # - .*/\api\/v1\/.*?\/login + api: [ ] + + # The parameters configuration allows users to specify headers, query parameters, and body keys that should be excluded from IAST scans. + # Example: + # http_request_parameters: + # header: + # - X-Forwarded-For + # query: + # - username + # - password + # body: + # - account.email + # - account.contact + http_request_parameters: + # A list of HTTP header keys. If a request includes any headers with these keys, the corresponding IAST scan will be skipped. + header: [ ] + # A list of query parameter keys. The presence of these parameters in the request's query string will lead to skipping the IAST scan. + query: [ ] + # A list of keys within the request body. If these keys are found in the body content, the IAST scan will be omitted. + body: [ ] + + # The iast_detection_category configuration allows to specify which categories of vulnerabilities should not be detected by Security Agents. + # If any of these categories are set to true, Security Agents will not generate events or flag vulnerabilities for that category. + iast_detection_category: + insecure_settings: false + invalid_file_access: false + sql_injection: false + nosql_injection: false + ldap_injection: false + javascript_injection: false + command_injection: false + xpath_injection: false + ssrf: false + rxss: false + + # Deprecated!!! Instead, please use iast_detection_category to disable vulnerabilities category by IAST, + # These are the category of security events that can be detected. Set to false to disable detection of + # individual event types. Default is true for each event type. + detection: + rci: + enabled: true + rxss: + enabled: true + deserialization: + enabled: true + + # Slow transaction detection will report an event to New Relic ("SlowTransaction") whenever a Transaction's + # time exceeds the threshold value (in ms). A transaction will only be reported once and by default, only + # transactions that are in-process during a harvest cycle will be checked. Only the slowest single transaction + # will be reported even if multiple transactions exceed the threshold. + slow_transactions: + enabled: true + threshold: 600000 + + # If this is set to true, every transaction will be checked for exceeding the defined threshold on + # transaction completion. Note that this can be computationally expensive since a stack trace is sent + # with every SlowTransaction event, if a large number of transaction exceed the threshold. + evaluate_completed_transactions: false + + enable_custom_tracing: true + +# Application Environments +# ------------------------------------------ +# Environment specific settings are in this section. +# You can use the environment to override the default settings. +# For example, to change the app_name setting. +# Use -Dnewrelic.environment= on the Java startup command line +# to set the environment. +# The default environment is production. + +# NOTE if your application has other named environments, you should +# provide configuration settings for these environments here. + +development: + <<: *default_settings + app_name: Currency Converter (Development) + +test: + <<: *default_settings + app_name: Currency Converter (Test) + +production: + <<: *default_settings + +staging: + <<: *default_settings + app_name: Currency Converter (Staging) diff --git a/libraries-apm/new-relic/currency-converter/pom.xml b/libraries-apm/new-relic/currency-converter/pom.xml new file mode 100644 index 000000000000..4fbd36686930 --- /dev/null +++ b/libraries-apm/new-relic/currency-converter/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.1 + + + com.baeldung + currency-converter + 0.0.1 + Currency Converter + Currency Converter Demo + + + 21 + 21 + 21 + + + + + org.springframework.boot + spring-boot-starter-cache + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-logging + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + + com.newrelic.agent.java + newrelic-java + 8.17.0 + provided + zip + + + + com.newrelic.agent.java + newrelic-api + 8.17.0 + + + + diff --git a/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/CurrencyConverterApplication.java b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/CurrencyConverterApplication.java new file mode 100644 index 000000000000..d67d16741fdb --- /dev/null +++ b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/CurrencyConverterApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.currency_converter; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CurrencyConverterApplication { + + public static void main(String[] args) { + SpringApplication.run(CurrencyConverterApplication.class, args); + } +} diff --git a/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/config/AppConfig.java b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/config/AppConfig.java new file mode 100644 index 000000000000..05cf9f36c899 --- /dev/null +++ b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/config/AppConfig.java @@ -0,0 +1,13 @@ +package com.baeldung.currency_converter.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class AppConfig { + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/config/RedisConfig.java b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/config/RedisConfig.java new file mode 100644 index 000000000000..4ee4dea79290 --- /dev/null +++ b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/config/RedisConfig.java @@ -0,0 +1,36 @@ +package com.baeldung.currency_converter.config; + +import java.time.Duration; + +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericToStringSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@EnableCaching +public class RedisConfig { + @Bean + public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { + RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofSeconds(90)); + + return RedisCacheManager.builder(redisConnectionFactory) + .cacheDefaults(cacheConfiguration) + .build(); + } + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericToStringSerializer<>(Double.class)); + return template; + } +} diff --git a/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/controller/CurrencyConverterController.java b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/controller/CurrencyConverterController.java new file mode 100644 index 000000000000..42b31af9dfea --- /dev/null +++ b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/controller/CurrencyConverterController.java @@ -0,0 +1,24 @@ +package com.baeldung.currency_converter.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.currency_converter.service.CurrencyConverterService; + +@RestController +@RequestMapping("/api/currency") +public class CurrencyConverterController { + private final CurrencyConverterService currencyConverterService; + + public CurrencyConverterController(CurrencyConverterService currencyConverterService) { + this.currencyConverterService = currencyConverterService; + } + + @GetMapping("/convert") + public double convertCurrency(@RequestParam String targetCurrency, + @RequestParam double amount) { + return currencyConverterService.getConvertedAmount(targetCurrency, amount); + } +} diff --git a/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/dto/ExchangeRateResponse.java b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/dto/ExchangeRateResponse.java new file mode 100644 index 000000000000..6dc6c7ba4330 --- /dev/null +++ b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/dto/ExchangeRateResponse.java @@ -0,0 +1,62 @@ +package com.baeldung.currency_converter.dto; + +import java.util.Map; + +public class ExchangeRateResponse { + private String disclaimer; + private String license; + private Long timestamp; + private String base; + private Map rates; + + public String getDisclaimer() { + return disclaimer; + } + + public void setDisclaimer(String disclaimer) { + this.disclaimer = disclaimer; + } + + public String getLicense() { + return license; + } + + public void setLicense(String license) { + this.license = license; + } + + public Long getTimestamp() { + return timestamp; + } + + public void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } + + public String getBase() { + return base; + } + + public void setBase(String base) { + this.base = base; + } + + public Map getRates() { + return rates; + } + + public void setRates(Map rates) { + this.rates = rates; + } + + @Override + public String toString() { + return "ExchangeRateResponse{" + + "disclaimer='" + disclaimer + '\'' + + ", license='" + license + '\'' + + ", timestamp=" + timestamp + + ", base='" + base + '\'' + + ", rates=" + rates + + '}'; + } +} diff --git a/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/service/CurrencyConverterService.java b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/service/CurrencyConverterService.java new file mode 100644 index 000000000000..7bf24d2a3b2d --- /dev/null +++ b/libraries-apm/new-relic/currency-converter/src/main/java/com/baeldung/currency_converter/service/CurrencyConverterService.java @@ -0,0 +1,114 @@ +package com.baeldung.currency_converter.service; + +import org.springframework.stereotype.Service; +import com.baeldung.currency_converter.dto.ExchangeRateResponse; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Trace; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Value; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.RedisTemplate; +import java.time.Duration; + +@Service +public class CurrencyConverterService { + private static final Logger logger = LoggerFactory.getLogger(CurrencyConverterService.class); + + @Value("${openexchangerates.url}") + private String openApiUrl; + + @Value("${openexchangerates.app_id}") + private String openApiAppId; + + @Value("${openexchangerates.base_currency}") + public String baseCurrency; + + private final OkHttpClient client; + private final ObjectMapper objectMapper; + private final RedisTemplate redisTemplate; + + public CurrencyConverterService(RedisTemplate redisTemplate) { + this.client = new OkHttpClient(); + this.objectMapper = new ObjectMapper(); + this.redisTemplate = redisTemplate; + } + + @Trace(metricName="CurrencyConversionCalc") + public double getConvertedAmount(String targetCurrency, double amount) { + String cacheKey = baseCurrency + "-" + targetCurrency; + + // Try to get rate from cache first + Double cachedRate = redisTemplate.opsForValue().get(cacheKey); + if (cachedRate != null) { + logger.info("Cache hit for key: {}", cacheKey); + return amount * cachedRate; + } else { + logger.info("Cache miss for key: {}, fetching from API", cacheKey); + NewRelic.incrementCounter("Custom/CacheMisses"); + } + + // Original API call logic + String url = String.format("%s/latest.json?app_id=%s", openApiUrl, openApiAppId); + logger.info("Fetching exchange rates from {}", url); + + try { + Request request = new Request.Builder() + .url(url) + .build(); + + long startTime = System.nanoTime(); + + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new RuntimeException("Unexpected response code: " + response); + } + + long durationInMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); + NewRelic.addCustomParameter("RatesAPI/ResponseTime", durationInMillis); + + + String rawResponse = response.body().string(); + ExchangeRateResponse exchangeResponse = objectMapper.readValue(rawResponse, ExchangeRateResponse.class); + + logger.info("Response headers:"); + response.headers().toMultimap().forEach((k, v) -> logger.info("{}: {}", k, v)); + + String eTagHeaderField = response.header("ETag"); + logger.info(eTagHeaderField); + + NewRelic.addCustomParameter(cacheKey, eTagHeaderField); + + if (exchangeResponse != null && exchangeResponse.getRates() != null) { + Map rates = exchangeResponse.getRates(); + Double targetRate = rates.get(targetCurrency); + + if (targetRate == null) { + logger.error("Target currency {} not found in rates", targetCurrency); + throw new IllegalArgumentException("Target currency not found in rates."); + } + + // Cache the rate before returning + redisTemplate.opsForValue().set(cacheKey, targetRate, Duration.ofHours(24)); + logger.info("Cached exchange rate for key: {}", cacheKey); + + return amount * targetRate; + } + + logger.error("Failed to get exchange rate response"); + throw new RuntimeException("Could not retrieve exchange rate."); + } + } catch (Exception e) { + logger.error("Error converting currency: {}", e.getMessage(), e); + NewRelic.noticeError(e); + throw new RuntimeException("Error converting currency", e); + } + } +} diff --git a/libraries-apm/new-relic/currency-converter/src/main/resources/application.properties b/libraries-apm/new-relic/currency-converter/src/main/resources/application.properties new file mode 100644 index 000000000000..e0021bc91925 --- /dev/null +++ b/libraries-apm/new-relic/currency-converter/src/main/resources/application.properties @@ -0,0 +1,15 @@ +spring.application.name=Currency Converter + +# Redis Configuration +spring.redis.host=localhost +spring.redis.port=6379 +# spring.redis.password= +spring.redis.timeout=2000 + +# Cache Configuration +spring.cache.type=redis + +# Open Exchange Rates API Configuration +openexchangerates.url=https://openexchangerates.org +openexchangerates.app_id= +openexchangerates.base_currency=USD \ No newline at end of file diff --git a/libraries-apm/new-relic/pom.xml b/libraries-apm/new-relic/pom.xml new file mode 100644 index 000000000000..f7076cbaaa53 --- /dev/null +++ b/libraries-apm/new-relic/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + com.baeldung.new-relic + new-relic + pom + new-relic + + + com.baeldung + libraries-apm + 1.0.0-SNAPSHOT + + + + currency-converter + + + \ No newline at end of file diff --git a/libraries-apm/pom.xml b/libraries-apm/pom.xml new file mode 100644 index 000000000000..f4f358fd0de3 --- /dev/null +++ b/libraries-apm/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + libraries-apm + pom + libraries-apm + + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + + + + new-relic + + + \ No newline at end of file