22
33import java .util .concurrent .TimeUnit ;
44
5- import jakarta .enterprise .context .ApplicationScoped ;
6- import jakarta .enterprise .event .Observes ;
7- import jakarta .enterprise .inject .Instance ;
8- import jakarta .inject .Inject ;
9-
105import dev .langchain4j .observability .api .event .InputGuardrailExecutedEvent ;
116import dev .langchain4j .observability .api .event .OutputGuardrailExecutedEvent ;
127import io .micrometer .core .instrument .Counter ;
13- import io .micrometer .core .instrument .MeterRegistry ;
8+ import io .micrometer .core .instrument .Metrics ;
149import io .micrometer .core .instrument .Timer ;
15- import io .quarkus .arc .Unremovable ;
1610
1711/**
18- * Observes guardrail execution events and records metrics with detailed tags.
12+ * Support class to observes guardrail execution events and records metrics with detailed tags.
1913 * <p>
2014 * This observer listens to {@link InputGuardrailExecutedEvent} and {@link OutputGuardrailExecutedEvent}
2115 * and records both counter and timer metrics with the following tags:
2721 * <li><strong>outcome</strong>: The result of the guardrail execution ({@link GuardrailOutcome})</li>
2822 * </ul>
2923 * <p>
30- * This observer is only active when Micrometer is available in the application.
24+ * Metrics collection must only be enabled when Micrometer is available.
25+ * In practice, however, Arc always registers observers, regardless of whether
26+ * Micrometer is on the classpath or whether an observer bean is actually
27+ * registered. As a result, an observer may attempt to record metrics when
28+ * Micrometer is missing, which can trigger runtime errors and even break
29+ * native image compilation.
30+ * </p>
31+ *
32+ * <p>
33+ * To prevent such failures, this class is only referenced by a bean that is
34+ * conditionally generated when Micrometer support is detected.
35+ * This class is only providing static methods, so simplify the generation logic.
36+ * </p>
3137 */
32- @ ApplicationScoped
33- @ Unremovable
34- public class GuardrailMetricsObserver {
35-
36- @ Inject
37- Instance <MeterRegistry > registry ;
38+ public class GuardrailMetricsObserverSupport {
3839
3940 /**
4041 * Observes input guardrail execution events and records metrics.
4142 *
4243 * @param event the input guardrail executed event
4344 */
44- public void onInputGuardrailExecuted (@ Observes InputGuardrailExecutedEvent event ) {
45- if (registry .isUnsatisfied ()) {
46- return ;
47- }
48-
45+ public static void onInputGuardrailExecuted (InputGuardrailExecutedEvent event ) {
4946 String aiServiceName = event .invocationContext ().interfaceName ();
5047 String methodName = event .invocationContext ().methodName ();
5148 String guardrailName = sanitize (event .guardrailClass ().getName ());
@@ -60,11 +57,7 @@ public void onInputGuardrailExecuted(@Observes InputGuardrailExecutedEvent event
6057 *
6158 * @param event the output guardrail executed event
6259 */
63- public void onOutputGuardrailExecuted (@ Observes OutputGuardrailExecutedEvent event ) {
64- if (registry .isUnsatisfied ()) {
65- return ;
66- }
67-
60+ public static void onOutputGuardrailExecuted (OutputGuardrailExecutedEvent event ) {
6861 String aiServiceName = event .invocationContext ().interfaceName ();
6962 String methodName = event .invocationContext ().methodName ();
7063 String guardrailName = sanitize (event .guardrailClass ().getName ());
@@ -74,7 +67,7 @@ public void onOutputGuardrailExecuted(@Observes OutputGuardrailExecutedEvent eve
7467 recordMetrics (GuardrailType .OUTPUT , aiServiceName , methodName , guardrailName , outcome , durationNanos );
7568 }
7669
77- private String sanitize (String simpleName ) {
70+ private static String sanitize (String simpleName ) {
7871 if (simpleName == null ) {
7972 return null ;
8073 }
@@ -84,11 +77,9 @@ private String sanitize(String simpleName) {
8477 return simpleName ;
8578 }
8679
87- private void recordMetrics (GuardrailType guardrailType , String aiServiceName , String methodName ,
80+ private static void recordMetrics (GuardrailType guardrailType , String aiServiceName , String methodName ,
8881 String guardrailName , GuardrailOutcome outcome , long durationNanos ) {
8982
90- MeterRegistry meterRegistry = registry .get ();
91-
9283 Counter .builder ("guardrail.invoked" )
9384 .description ("Number of guardrail invocations" )
9485 .tags (
@@ -97,7 +88,7 @@ private void recordMetrics(GuardrailType guardrailType, String aiServiceName, St
9788 "guardrail" , guardrailName ,
9889 "guardrail.type" , guardrailType .getValue (),
9990 "outcome" , outcome .getValue ())
100- .register (meterRegistry )
91+ .register (Metrics . globalRegistry )
10192 .increment ();
10293
10394 Timer .builder ("guardrail.timed" )
@@ -109,11 +100,11 @@ private void recordMetrics(GuardrailType guardrailType, String aiServiceName, St
109100 "outcome" , outcome .getValue ())
110101 .publishPercentiles (new double [] { 0.75 , 0.95 , 0.99 })
111102 .publishPercentileHistogram (true )
112- .register (meterRegistry )
103+ .register (Metrics . globalRegistry )
113104 .record (durationNanos , TimeUnit .NANOSECONDS );
114105 }
115106
116- private GuardrailOutcome determineInputGuardrailOutcome (InputGuardrailExecutedEvent event ) {
107+ private static GuardrailOutcome determineInputGuardrailOutcome (InputGuardrailExecutedEvent event ) {
117108 // Check if the guardrail execution was successful
118109 if (event .result ().isSuccess ()) {
119110 return GuardrailOutcome .SUCCESS ;
@@ -122,7 +113,7 @@ private GuardrailOutcome determineInputGuardrailOutcome(InputGuardrailExecutedEv
122113 }
123114 }
124115
125- private GuardrailOutcome determineOutputGuardrailOutcome (OutputGuardrailExecutedEvent event ) {
116+ private static GuardrailOutcome determineOutputGuardrailOutcome (OutputGuardrailExecutedEvent event ) {
126117 // Check the result type to determine the outcome
127118 if (event .result ().isSuccess ()) {
128119 return GuardrailOutcome .SUCCESS ;
0 commit comments