001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.server.observation; 018 019 020import java.util.concurrent.atomic.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022 023/** 024 * Static facade over an explicitly-installed default {@link Observer} for ergonomic custom 025 * (non-request) observations. 026 * 027 * <p> 028 * {@code juneau-rest-server} has no method-interception / AOP substrate, so custom observations are 029 * <b>explicit</b>: wrap the block of work in a try-with-resources over {@link #observe(String, String)}. 030 * This mirrors how the request path is instrumented (the framework opens / closes the observation around 031 * the {@code @RestOp} handler) but lets application code instrument arbitrary work. 032 * 033 * <h5 class='topic'>Installing a backend</h5> 034 * 035 * <p> 036 * The default observer is {@link Observer#NOOP} until a backend is installed via {@link #install(Observer)} 037 * — typically once at startup, after the application's {@code MetricsRecorder} / {@code TracerHook} 038 * beans are known. Until then (and in any process that never installs one) {@link #observe(String, String)} 039 * hands back {@link Observation#NOOP} and costs nothing. 040 * 041 * <p class='bjava'> 042 * <jc>// At startup, once the observability beans are built:</jc> 043 * Observations.<jsm>install</jsm>(<jk>new</jk> Observer(<jv>metricsRecorder</jv>, <jv>tracerHook</jv>)); 044 * 045 * <jc>// Anywhere in application code:</jc> 046 * <jk>try</jk> (Observation <jv>o</jv> = Observations.<jsm>observe</jsm>(<js>"order.load"</js>, <js>"team=payments"</js>)) { 047 * <jk>return</jk> loadOrder(<jv>id</jv>); 048 * } <jk>catch</jk> (RuntimeException <jv>e</jv>) { 049 * <jv>o</jv>.setError(<jv>e</jv>); 050 * <jk>throw</jk> <jv>e</jv>; 051 * } 052 * </p> 053 * 054 * <p> 055 * Code that prefers explicit dependency injection over a global can hold an {@link Observer} directly 056 * (resolved from the {@code RestContext} bean store) and call {@link Observer#start(String, String)} — 057 * this facade is a convenience, not the only entry point. 058 * 059 * <h5 class='section'>See Also:</h5><ul> 060 * <li class='jc'>{@link Observer} 061 * <li class='jc'>{@link Observation} 062 * </ul> 063 * 064 * @since 10.0.0 065 */ 066@SuppressWarnings({ 067 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 068 "resource" // observe(...) returns the Observation handle for the caller's try-with-resources; closing it here is wrong. 069}) 070public class Observations { 071 072 private static final AtomicReference<Observer> defaultObserver = new AtomicReference<>(Observer.NOOP); 073 074 private Observations() {} 075 076 /** 077 * Installs the process-wide default {@link Observer} used by {@link #observe(String, String)}. 078 * 079 * <p> 080 * Intended to be called once at application startup. Passing {@link Observer#NOOP} (or calling 081 * {@link #reset()}) restores the no-op default. 082 * 083 * @param observer The observer to install. Must not be <jk>null</jk>. 084 */ 085 public static void install(Observer observer) { 086 defaultObserver.set(reqnn("observer", observer)); 087 } 088 089 /** 090 * Resets the process-wide default observer back to {@link Observer#NOOP}. 091 */ 092 public static void reset() { 093 defaultObserver.set(Observer.NOOP); 094 } 095 096 /** 097 * Returns the currently-installed default {@link Observer}. 098 * 099 * @return The default observer; {@link Observer#NOOP} if none was installed. Never <jk>null</jk>. 100 */ 101 public static Observer observer() { 102 return defaultObserver.get(); 103 } 104 105 /** 106 * Starts a custom observation against the installed default {@link Observer}. 107 * 108 * @param name The observation name — used as both the span name and the metric timer name. 109 * Must not be <jk>null</jk> or blank. 110 * @param tags Additional metric tags as comma-separated {@code key=value} pairs, or <jk>null</jk> / 111 * empty for none. 112 * @return The started {@link Observation} (a try-with-resources handle). Never <jk>null</jk>. 113 */ 114 public static Observation observe(String name, String tags) { 115 return defaultObserver.get().start(name, tags); 116 } 117 118 /** 119 * Starts a custom observation with no additional tags. 120 * 121 * @param name The observation name. Must not be <jk>null</jk> or blank. 122 * @return The started {@link Observation}. Never <jk>null</jk>. 123 */ 124 public static Observation observe(String name) { 125 return defaultObserver.get().start(name, ""); 126 } 127}