├── .bazelversion ├── .gitignore ├── grpc ├── src │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── com.google.common.flogger.context.ContextDataProvider │ │ └── java │ │ │ └── com │ │ │ └── google │ │ │ └── common │ │ │ └── flogger │ │ │ └── grpc │ │ │ └── package-info.java │ └── test │ │ └── java │ │ └── com │ │ └── google │ │ └── common │ │ └── flogger │ │ └── grpc │ │ └── GrpcContextDataProviderTest.java └── BUILD ├── slf4j ├── src │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── com.google.common.flogger.backend.system.BackendFactory │ │ └── java │ │ │ └── com │ │ │ └── google │ │ │ └── common │ │ │ └── flogger │ │ │ └── backend │ │ │ └── slf4j │ │ │ └── Slf4jBackendFactory.java │ └── test │ │ └── java │ │ └── com │ │ └── google │ │ └── common │ │ └── flogger │ │ └── backend │ │ └── slf4j │ │ └── Slf4jBackendFactoryTest.java └── BUILD ├── log4j2 ├── src │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── com.google.common.flogger.backend.system.BackendFactory │ │ └── java │ │ │ └── com │ │ │ └── google │ │ │ └── common │ │ │ └── flogger │ │ │ └── backend │ │ │ └── log4j2 │ │ │ ├── Log4j2LoggerBackend.java │ │ │ └── Log4j2BackendFactory.java │ └── test │ │ └── java │ │ └── com │ │ └── google │ │ └── common │ │ └── flogger │ │ └── backend │ │ └── log4j2 │ │ ├── Log4j2BackendFactoryTest.java │ │ └── ValueQueueTest.java └── BUILD ├── .bazelrc ├── release ├── install-local-snapshot.sh ├── deploy-to-maven-central.sh └── execute-deploy.sh ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── AUTHORS ├── api ├── proguard.cfg ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── google │ │ │ └── common │ │ │ └── flogger │ │ │ ├── util │ │ │ ├── package-info.java │ │ │ ├── StackGetter.java │ │ │ ├── ThrowableStackGetter.java │ │ │ ├── JavaLangAccessStackGetter.java │ │ │ ├── RecursionDepth.java │ │ │ ├── Checks.java │ │ │ └── StackWalkerStackGetter.java │ │ │ ├── backend │ │ │ ├── package-info.java │ │ │ ├── system │ │ │ │ ├── package-info.java │ │ │ │ ├── SimpleLoggerBackend.java │ │ │ │ ├── SystemClock.java │ │ │ │ ├── SimpleBackendFactory.java │ │ │ │ ├── LoggingContext.java │ │ │ │ ├── StackBasedCallerFinder.java │ │ │ │ ├── Clock.java │ │ │ │ └── SimpleLogRecord.java │ │ │ ├── LogSiteFormatter.java │ │ │ ├── LoggingException.java │ │ │ ├── TemplateContext.java │ │ │ ├── LogSiteFormatters.java │ │ │ └── LogMessageFormatter.java │ │ │ ├── context │ │ │ ├── package-info.java │ │ │ └── ScopeType.java │ │ │ ├── package-info.java │ │ │ ├── parser │ │ │ ├── package-info.java │ │ │ ├── DefaultBraceStyleMessageParser.java │ │ │ └── MessageParser.java │ │ │ ├── parameter │ │ │ ├── package-info.java │ │ │ ├── DateTimeParameter.java │ │ │ ├── Parameter.java │ │ │ └── ParameterVisitor.java │ │ │ ├── LogSiteKey.java │ │ │ ├── LazyArg.java │ │ │ ├── LoggingScopeProvider.java │ │ │ ├── SpecializedLogSiteKey.java │ │ │ ├── LogSiteStackTrace.java │ │ │ ├── LazyArgs.java │ │ │ ├── StackBasedLogSite.java │ │ │ ├── CountingRateLimiter.java │ │ │ └── SamplingRateLimiter.java │ └── test │ │ └── java │ │ └── com │ │ └── google │ │ └── common │ │ └── flogger │ │ ├── testing │ │ ├── package-info.java │ │ ├── FakeLogRecord.java │ │ ├── FakeMetadata.java │ │ ├── FormatTypeSubject.java │ │ ├── FakeLogSite.java │ │ ├── FakeLoggerBackend.java │ │ └── TestLogger.java │ │ ├── LoggingScopeTest.java │ │ ├── util │ │ ├── ThrowableStackGetterTest.java │ │ ├── StackWalkerStackGetterTest.java │ │ ├── JavaLangAccessStackGetterTest.java │ │ ├── StackGetterTestUtil.java │ │ └── CallerFinderTest.java │ │ ├── LogSitesTest.java │ │ ├── LogSiteStackTraceTest.java │ │ ├── parameter │ │ ├── ParameterTest.java │ │ └── SimpleParameterTest.java │ │ ├── backend │ │ ├── system │ │ │ └── DefaultPlatformServiceLoadingTest.java │ │ ├── MetadataKeyValueHandlersTest.java │ │ └── LogSiteFormattersTest.java │ │ ├── SpecializedLogSiteKeyTest.java │ │ ├── parser │ │ ├── DefaultBraceStyleMessageParserTest.java │ │ └── DefaultPrintfMessageParserTest.java │ │ ├── SamplingRateLimiterTest.java │ │ ├── LogSiteTest.java │ │ ├── LogPerBucketingStrategyTest.java │ │ └── FluentLoggerTest.java └── build_defs.bzl ├── tools ├── maven.bzl ├── BUILD └── pom-template.xml ├── MODULE.bazel ├── google ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── google │ │ │ └── common │ │ │ └── flogger │ │ │ ├── GoogleLoggingApi.java │ │ │ └── GoogleLogContext.java │ └── test │ │ └── java │ │ └── com │ │ └── google │ │ └── common │ │ └── flogger │ │ └── PackageSanityTest.java └── BUILD └── CONTRIBUTING.md /.bazelversion: -------------------------------------------------------------------------------- 1 | 8.4.2 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | .DS_Store 4 | /bazel-* 5 | 6 | -------------------------------------------------------------------------------- /grpc/src/main/resources/META-INF/services/com.google.common.flogger.context.ContextDataProvider: -------------------------------------------------------------------------------- 1 | com.google.common.flogger.grpc.GrpcContextDataProvider -------------------------------------------------------------------------------- /slf4j/src/main/resources/META-INF/services/com.google.common.flogger.backend.system.BackendFactory: -------------------------------------------------------------------------------- 1 | com.google.common.flogger.backend.slf4j.Slf4jBackendFactory -------------------------------------------------------------------------------- /log4j2/src/main/resources/META-INF/services/com.google.common.flogger.backend.system.BackendFactory: -------------------------------------------------------------------------------- 1 | com.google.common.flogger.backend.log4j2.Log4j2BackendFactory -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | # Include debug info in the compiled jars 2 | build --javacopt=-g 3 | build --host_javacopt=-g 4 | 5 | build --java_runtime_version=21 --javacopt='-source 8 -target 8' 6 | -------------------------------------------------------------------------------- /release/install-local-snapshot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | echo -e "Installing maven snapshot locally...\n" 6 | 7 | bash $(dirname $0)/execute-deploy.sh \ 8 | "install:install-file" \ 9 | "LOCAL-SNAPSHOT" 10 | 11 | echo -e "Installed local snapshot" 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | groups: 8 | github-actions: 9 | applies-to: version-updates 10 | patterns: 11 | - "*" 12 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of Flogger authors for copyright purposes. 2 | # 3 | # This does not necessarily list everyone who has contributed code, since in 4 | # some cases, their employer may be the copyright holder. To see the full list 5 | # of contributors, see the revision history in source control. 6 | Google Inc. 7 | and other contributors -------------------------------------------------------------------------------- /api/proguard.cfg: -------------------------------------------------------------------------------- 1 | # Don't warn about the platforms not being found - they aren't available 2 | # everywhere, by design. Platforms are loaded dynamically at runtime. 3 | -dontwarn com.google.common.flogger.backend.system.DefaultPlatform* 4 | -dontwarn sun.misc.SharedSecrets 5 | -dontwarn sun.misc.JavaLangAccess 6 | -dontwarn java.lang.StackWalker* 7 | # This is here for the dropWhile method call in StackWalkerStackGetter but it's 8 | # not possible to filter for methods only. 9 | -dontwarn java.util.stream.Stream 10 | -------------------------------------------------------------------------------- /tools/maven.bzl: -------------------------------------------------------------------------------- 1 | """Macros to simplify generating maven files. 2 | """ 3 | 4 | load("@google_bazel_common//tools/maven:pom_file.bzl", default_pom_file = "pom_file") 5 | 6 | def pom_file(name, targets, artifact_name, artifact_id, **kwargs): 7 | default_pom_file( 8 | name = name, 9 | targets = targets, 10 | preferred_group_ids = [ 11 | "com.google.flogger", 12 | "com.google", 13 | ], 14 | template_file = "//tools:pom-template.xml", 15 | substitutions = { 16 | "{artifact_name}": artifact_name, 17 | "{artifact_id}": artifact_id, 18 | }, 19 | **kwargs 20 | ) 21 | -------------------------------------------------------------------------------- /release/deploy-to-maven-central.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | if [ $# -lt 2 ]; then 6 | echo "usage $0 [ ...]" 7 | exit 1; 8 | fi 9 | key=$1 10 | version_name=$2 11 | shift 2 12 | 13 | if [[ "$version_name" =~ " " ]]; then 14 | echo "Version name must not have any spaces" 15 | exit 3 16 | fi 17 | 18 | bazelisk test //... 19 | 20 | bash $(dirname $0)/execute-deploy.sh \ 21 | "gpg:sign-and-deploy-file" \ 22 | "$version_name" \ 23 | "-DrepositoryId=sonatype-nexus-staging" \ 24 | "-Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/" \ 25 | "-Dgpg.keyname=${key}" 26 | 27 | # TODO(b/27549364): add a tag and publish Javadoc to the docs site 28 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/util/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @CheckReturnValue 18 | package com.google.common.flogger.util; 19 | 20 | import com.google.errorprone.annotations.CheckReturnValue; 21 | -------------------------------------------------------------------------------- /grpc/src/main/java/com/google/common/flogger/grpc/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @CheckReturnValue 18 | package com.google.common.flogger.grpc; 19 | 20 | import com.google.errorprone.annotations.CheckReturnValue; 21 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @CheckReturnValue 18 | package com.google.common.flogger.backend; 19 | 20 | import com.google.errorprone.annotations.CheckReturnValue; 21 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/context/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @CheckReturnValue 18 | package com.google.common.flogger.context; 19 | 20 | import com.google.errorprone.annotations.CheckReturnValue; 21 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/testing/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @CheckReturnValue 18 | package com.google.common.flogger.testing; 19 | 20 | import com.google.errorprone.annotations.CheckReturnValue; 21 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/system/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @CheckReturnValue 18 | package com.google.common.flogger.backend.system; 19 | 20 | import com.google.errorprone.annotations.CheckReturnValue; 21 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2024 The Flogger Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | bazel_dep(name = "rules_java", version = "8.15.2") 16 | bazel_dep(name = "google_bazel_common") 17 | git_override( 18 | module_name = "google_bazel_common", 19 | commit = "692efe797f25b180a30354a4f1289b7c08d7fa86", 20 | remote = "https://github.com/google/bazel-common", 21 | ) 22 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Flogger is a fluent API for writing log messages. 19 | * 20 | *

This package is a part of the open-source 21 | * Flogger library. 22 | */ 23 | @CheckReturnValue 24 | package com.google.common.flogger; 25 | 26 | import com.google.errorprone.annotations.CheckReturnValue; 27 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/parser/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Flogger is a fluent API for writing log messages. 19 | * 20 | *

This package is a part of the open-source 21 | * Flogger library. 22 | */ 23 | @CheckReturnValue 24 | package com.google.common.flogger.parser; 25 | 26 | import com.google.errorprone.annotations.CheckReturnValue; 27 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/parameter/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Flogger is a fluent API for writing log messages. 19 | * 20 | *

This package is a part of the open-source 21 | * Flogger library. 22 | */ 23 | @CheckReturnValue 24 | package com.google.common.flogger.parameter; 25 | 26 | import com.google.errorprone.annotations.CheckReturnValue; 27 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/LoggingScopeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.junit.runners.JUnit4; 24 | 25 | @RunWith(JUnit4.class) 26 | public class LoggingScopeTest { 27 | @Test 28 | public void testDifferentLabelsAreDistinctScopes() { 29 | // Using the same label doesn't make scopes equivalent. 30 | assertThat(LoggingScope.create("foo")).isNotEqualTo(LoggingScope.create("foo")); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/util/ThrowableStackGetterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.util; 18 | 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | import org.junit.runners.JUnit4; 22 | 23 | @RunWith(JUnit4.class) 24 | public class ThrowableStackGetterTest { 25 | @Test 26 | public void testCallerOf() { 27 | StackGetterTestUtil.runTestCallerOf(new ThrowableStackGetter()); 28 | } 29 | 30 | @Test 31 | public void testCallerOfBadOffset() { 32 | StackGetterTestUtil.runTestCallerOfBadOffset(new ThrowableStackGetter()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/LogSiteFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend; 18 | 19 | import com.google.common.flogger.LogSite; 20 | import com.google.errorprone.annotations.CanIgnoreReturnValue; 21 | 22 | /** Interface for custom LogSite formatting. */ 23 | public interface LogSiteFormatter { 24 | 25 | /** 26 | * Appends log site information to a buffer. 27 | * 28 | * @param logSite the log site to be appended (ignored if {@link LogSite#INVALID}). 29 | * @param out the destination buffer. 30 | * @return whether the logSite was appended. 31 | */ 32 | @CanIgnoreReturnValue 33 | boolean appendLogSite(LogSite logSite, StringBuilder out); 34 | } 35 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/LogSiteKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | /** 20 | * A tagging interface to mark implementations that are suitable for use as a key for looking up 21 | * per log site persistent state. Normally the class used is just {@link LogSite} but other, more 22 | * specific, keys can be used. There are no method requirements on this interface, but the instance 23 | * must have correct {@code equals()}, {@code hashCode()} and {@code toString()} implementations 24 | * and must be at least as unique as the associated {@code LogSite} (i.e. two keys created for 25 | * different log sites must never be equal). 26 | */ 27 | public interface LogSiteKey {} 28 | 29 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/LazyArg.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import org.jspecify.annotations.Nullable; 20 | 21 | /** 22 | * Functional interface for allowing lazily evaluated arguments to be supplied to Flogger. This 23 | * allows callers to defer argument evaluation efficiently when: 24 | * 25 | *

29 | */ 30 | public interface LazyArg { 31 | /** 32 | * Computes a value to use as a log argument. This method is invoked once the Flogger library has 33 | * determined that logging will occur, and the returned value is used in place of the {@code 34 | * LazyArg} instance that was passed into the log statement. 35 | */ 36 | @Nullable T evaluate(); 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | name: "JDK ${{ matrix.java }}" 14 | strategy: 15 | matrix: 16 | java: [8, 11, 22] 17 | runs-on: ubuntu-latest 18 | steps: 19 | # Cancel any previous runs for the same branch that are still running. 20 | - name: 'Cancel previous runs' 21 | uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # 0.12.1 22 | with: 23 | access_token: ${{ github.token }} 24 | - name: 'Check out repository' 25 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 26 | - name: 'Cache Bazel dependencies' 27 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 28 | with: 29 | path: ~/.cache/bazel/*/*/external 30 | key: bazel-${{ hashFiles('MODULE.bazel') }} 31 | restore-keys: | 32 | bazel- 33 | - name: 'Set up JDK ${{ matrix.java }}' 34 | uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 35 | with: 36 | java-version: ${{ matrix.java }} 37 | distribution: 'temurin' 38 | - name: 'Test' 39 | shell: bash 40 | run: bazelisk test --lockfile_mode=error --test_output=errors //... 41 | - name: 'Test Deploy Script' 42 | if: matrix.java == 22 43 | shell: bash 44 | run: release/install-local-snapshot.sh 45 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/LoggingScopeProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import org.jspecify.annotations.Nullable; 20 | 21 | /** 22 | * Provides a scope to a log statement via the {@link LogContext#per(LoggingScopeProvider)} method. 23 | * 24 | *

This interface exists to avoid needing to pass specific instances of {@link LoggingScope} 25 | * around in user code. The scope provider can lookup the correct scope instance for the current 26 | * thread, and different providers can provide different types of scope (e.g. you can have a 27 | * provider for "request" scopes and a provider for "sub-task" scopes) 28 | */ 29 | public interface LoggingScopeProvider { 30 | /** 31 | * Returns the current scope (most likely via global or thread local state) or {@code null} if 32 | * there is no current scope. 33 | */ 34 | @Nullable LoggingScope getCurrentScope(); 35 | } 36 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/system/SimpleLoggerBackend.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.system; 18 | 19 | import com.google.common.flogger.backend.LogData; 20 | import com.google.common.flogger.backend.Platform; 21 | import java.util.logging.Logger; 22 | 23 | /** A logging backend that uses the {@code java.util.logging} classes to output log statements. */ 24 | public class SimpleLoggerBackend extends AbstractBackend { 25 | 26 | public SimpleLoggerBackend(Logger logger) { 27 | super(logger); 28 | } 29 | 30 | @Override 31 | public void log(LogData data) { 32 | log(SimpleLogRecord.create(data, Platform.getInjectedMetadata()), data.wasForced()); 33 | } 34 | 35 | @Override 36 | public void handleError(RuntimeException error, LogData badData) { 37 | log(SimpleLogRecord.error(error, badData, Platform.getInjectedMetadata()), badData.wasForced()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/util/StackWalkerStackGetterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.util; 18 | 19 | import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; 20 | import static org.junit.Assume.assumeTrue; 21 | 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.JUnit4; 25 | 26 | @RunWith(JUnit4.class) 27 | public class StackWalkerStackGetterTest { 28 | 29 | @Test 30 | public void testCallerOf() { 31 | assumeJava9OrAbove(); 32 | StackGetterTestUtil.runTestCallerOf(new StackWalkerStackGetter()); 33 | } 34 | 35 | @Test 36 | public void testCallerOfBadOffset() { 37 | assumeJava9OrAbove(); 38 | StackGetterTestUtil.runTestCallerOfBadOffset(new StackWalkerStackGetter()); 39 | } 40 | 41 | private static void assumeJava9OrAbove() { 42 | assumeTrue(JAVA_SPECIFICATION_VERSION.value().compareTo("1.8") > 0); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /slf4j/src/test/java/com/google/common/flogger/backend/slf4j/Slf4jBackendFactoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.slf4j; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import com.google.common.collect.ImmutableList; 22 | import com.google.common.flogger.backend.system.BackendFactory; 23 | import java.util.ServiceLoader; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.junit.runners.JUnit4; 27 | 28 | /** Tests specific to the {@link Slf4j2BackendFactory} implementation. */ 29 | @RunWith(JUnit4.class) 30 | public final class Slf4jBackendFactoryTest { 31 | 32 | @Test 33 | public void testCanLoadWithServiceLoader() { 34 | ImmutableList factories = 35 | ImmutableList.copyOf(ServiceLoader.load(BackendFactory.class)); 36 | assertThat(factories).hasSize(1); 37 | assertThat(factories.get(0)).isInstanceOf(Slf4jBackendFactory.class); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/system/SystemClock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.system; 18 | 19 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 20 | 21 | /** 22 | * Default millisecond precision clock. 23 | * 24 | *

See class documentation in {@link Clock} for important implementation restrictions. 25 | */ 26 | public final class SystemClock extends Clock { 27 | private static final SystemClock INSTANCE = new SystemClock(); 28 | 29 | // Called during logging platform initialization; MUST NOT call any code that might log. 30 | public static SystemClock getInstance() { 31 | return INSTANCE; 32 | } 33 | 34 | private SystemClock() { } 35 | 36 | @Override 37 | public long getCurrentTimeNanos() { 38 | return MILLISECONDS.toNanos(System.currentTimeMillis()); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "Default millisecond precision clock"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /log4j2/src/test/java/com/google/common/flogger/backend/log4j2/Log4j2BackendFactoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.log4j2; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import com.google.common.collect.ImmutableList; 22 | import com.google.common.flogger.backend.system.BackendFactory; 23 | import java.util.ServiceLoader; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.junit.runners.JUnit4; 27 | 28 | /** Tests specific to the {@link Log4j2BackendFactory} implementation. */ 29 | @RunWith(JUnit4.class) 30 | public final class Log4j2BackendFactoryTest { 31 | 32 | @Test 33 | public void testCanLoadWithServiceLoader() { 34 | ImmutableList factories = 35 | ImmutableList.copyOf(ServiceLoader.load(BackendFactory.class)); 36 | assertThat(factories).hasSize(1); 37 | assertThat(factories.get(0)).isInstanceOf(Log4j2BackendFactory.class); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tools/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018 The Flogger Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") 16 | 17 | exports_files(["pom-template.xml"]) 18 | 19 | javadoc_library( 20 | name = "all_javadoc", 21 | testonly = 1, 22 | srcs = [ 23 | "//api:javadoc_sources", 24 | "//google:javadoc_sources", 25 | ], 26 | bottom_text = "Copyright © 2018– The Flogger Authors. All rights reserved.", 27 | doctitle = "Flogger: A Fluent Logging API for Java", 28 | exclude_packages = ["com.google.common.flogger.util"], 29 | external_javadoc_links = [ 30 | "https://google.github.io/guava/releases/24.1-jre/api/docs/", 31 | "https://google.github.io/truth/api/0.40/", 32 | "http://errorprone.info/api/latest/", 33 | "https://junit.org/junit4/javadoc/4.11/", 34 | ], 35 | root_packages = ["com.google.common.flogger"], 36 | deps = [ 37 | "//api", 38 | "//api:testing", 39 | "//google:flogger", 40 | ], 41 | ) 42 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/LoggingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend; 18 | 19 | import org.jspecify.annotations.Nullable; 20 | 21 | /** 22 | * Exception thrown when a log statement cannot be emitted correctly. This exception should only be 23 | * thrown by logger backend implementations which have opted not to handle specific issues. 24 | *

25 | * Typically a logger backend would only throw {@code LoggingException} in response to issues in 26 | * test code or other debugging environments. In production code, the backend should be configured 27 | * to emit a modified log statement which includes the error information. 28 | *

29 | * See also {@link LoggerBackend#handleError(RuntimeException, LogData)}. 30 | */ 31 | public class LoggingException extends RuntimeException { 32 | 33 | public LoggingException(@Nullable String message) { 34 | super(message); 35 | } 36 | 37 | public LoggingException(@Nullable String message, @Nullable Throwable cause) { 38 | super(message, cause); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /grpc/src/test/java/com/google/common/flogger/grpc/GrpcContextDataProviderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.grpc; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import com.google.common.collect.ImmutableList; 22 | import com.google.common.flogger.context.ContextDataProvider; 23 | import com.google.common.flogger.testing.AbstractScopedLoggingContextTest; 24 | import java.util.ServiceLoader; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.junit.runners.JUnit4; 28 | 29 | @RunWith(JUnit4.class) 30 | public class GrpcContextDataProviderTest extends AbstractScopedLoggingContextTest { 31 | 32 | @Override 33 | protected ContextDataProvider getImplementationUnderTest() { 34 | return GrpcContextDataProvider.getInstance(); 35 | } 36 | 37 | @Test 38 | public void testCanLoadWithServiceLoader() { 39 | ImmutableList providers = 40 | ImmutableList.copyOf(ServiceLoader.load(ContextDataProvider.class)); 41 | assertThat(providers).hasSize(1); 42 | assertThat(providers.get(0)).isInstanceOf(GrpcContextDataProvider.class); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /api/build_defs.bzl: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2024 The Flogger Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Utilities for building Flogger.""" 16 | 17 | def genjar(name, srcs = [], deps = [], **kwargs): 18 | """Compiles Java source files to a jar file using a genrule. 19 | 20 | Args: 21 | name: The name of the target. The output jar file will be named .jar. 22 | srcs: The Java source files to compile. 23 | deps: The Java libraries to depend on. 24 | **kwargs: Additional arguments to pass to genrule. 25 | """ 26 | native.genrule( 27 | name = name, 28 | srcs = srcs + deps, 29 | outs = [name + ".jar"], 30 | cmd = 31 | """ 32 | set -eu 33 | TMPDIR="$$(mktemp -d)" 34 | "$(JAVABASE)/bin/javac" -d "$$TMPDIR" \ 35 | -source 8 -target 8 -implicit:none \ 36 | -cp "{classpath}" {srcs} 37 | "$(JAVABASE)/bin/jar" cf "$@" -C "$$TMPDIR" . 38 | """.format( 39 | srcs = " ".join(["$(location %s)" % src for src in srcs]), 40 | classpath = ":".join(["$(location %s)" % dep for dep in deps]), 41 | ), 42 | **kwargs 43 | ) 44 | -------------------------------------------------------------------------------- /google/src/main/java/com/google/common/flogger/GoogleLoggingApi.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | /** 20 | * Google specific extensions to the fluent logging API. 21 | * 22 | *

If a team wishes to implement its own logging extensions, it must extend this API (in the same 23 | * way that this API was extended from LoggingApi) and extend {@link GoogleLogContext} similarly. 24 | * This should all be tied together by implementing an alternate implementation of GoogleLogger. 25 | * 26 | *

However, if you wish to extend the default logging API, please contact flogger-dev@ first. 27 | * 28 | * @param The api returned during method chaining (possibly an extension of this interface). 29 | */ 30 | // NOTE: new methods to this interface should be coordinated with google-java-format 31 | public interface GoogleLoggingApi> extends LoggingApi { 32 | /** 33 | * An implementation of {@link GoogleLoggingApi} which does nothing and discards all parameters. 34 | */ 35 | public static class NoOp> extends LoggingApi.NoOp 36 | implements GoogleLoggingApi {} 37 | } 38 | -------------------------------------------------------------------------------- /slf4j/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google Inc. All Rights Reserved. 2 | # 3 | # Description: 4 | # Flogger slf4j backend (google.github.io/flogger). 5 | 6 | load("@google_bazel_common//testing:test_defs.bzl", "gen_java_tests") 7 | load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") 8 | load("@rules_java//java:defs.bzl", "java_library") 9 | load("//tools:maven.bzl", "pom_file") 10 | 11 | SLF4J_BACKEND_SRCS = glob(["src/main/java/**/*.java"]) 12 | 13 | SLF4J_BACKEND_RESOURCES = glob(["src/main/resources/**"]) 14 | 15 | java_library( 16 | name = "slf4j_backend", 17 | srcs = SLF4J_BACKEND_SRCS, 18 | resources = SLF4J_BACKEND_RESOURCES, 19 | tags = ["maven_coordinates=com.google.flogger:flogger-slf4j-backend:${project.version}"], 20 | deps = [ 21 | "//api", 22 | "//api:system_backend", 23 | "@google_bazel_common//third_party/java/slf4j_api", 24 | ], 25 | ) 26 | 27 | pom_file( 28 | name = "pom", 29 | artifact_id = "flogger-slf4j-backend", 30 | artifact_name = "Flogger SLF4J Backend", 31 | targets = [":slf4j_backend"], 32 | ) 33 | 34 | javadoc_library( 35 | name = "javadoc", 36 | srcs = SLF4J_BACKEND_SRCS, 37 | root_packages = ["com.google.common.flogger.backend.slf4j"], 38 | deps = [":slf4j_backend"], 39 | ) 40 | 41 | # ---- Unit Tests ---- 42 | 43 | gen_java_tests( 44 | name = "slf4j_tests", 45 | srcs = glob(["src/test/java/**/*.java"]), 46 | deps = [ 47 | ":slf4j_backend", 48 | "//api", 49 | "//api:system_backend", 50 | "//api:testing", 51 | "@google_bazel_common//third_party/java/guava", 52 | "@google_bazel_common//third_party/java/junit", 53 | "@google_bazel_common//third_party/java/mockito", 54 | "@google_bazel_common//third_party/java/slf4j_api", 55 | "@google_bazel_common//third_party/java/truth", 56 | ], 57 | ) 58 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/system/SimpleBackendFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.system; 18 | 19 | import com.google.common.flogger.backend.LoggerBackend; 20 | import java.util.logging.Logger; 21 | 22 | /** 23 | * Default factory for creating logger backends. 24 | * 25 | *

See class documentation in {@link BackendFactory} for important implementation restrictions. 26 | */ 27 | public final class SimpleBackendFactory extends BackendFactory { 28 | private static final BackendFactory INSTANCE = new SimpleBackendFactory(); 29 | 30 | // Called during logging platform initialization; MUST NOT call any code that might log. 31 | public static BackendFactory getInstance() { 32 | return INSTANCE; 33 | } 34 | 35 | private SimpleBackendFactory() {} 36 | 37 | @Override 38 | public LoggerBackend create(String loggingClass) { 39 | // TODO(b/27920233): Strip inner/nested classes when deriving logger name. 40 | Logger logger = Logger.getLogger(loggingClass.replace('$', '.')); 41 | return new SimpleLoggerBackend(logger); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | // This should probably be changed (it's not useful if it doesn't contain the class name). 47 | return "Default logger backend factory"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/util/JavaLangAccessStackGetterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.util; 18 | 19 | import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; 20 | import static org.junit.Assume.assumeTrue; 21 | 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.JUnit4; 25 | 26 | @RunWith(JUnit4.class) 27 | public class JavaLangAccessStackGetterTest { 28 | 29 | @Test 30 | public void testCallerOf() { 31 | assumeJava8(); 32 | StackGetterTestUtil.runTestCallerOf(getJavaLangAccessStackGetter()); 33 | } 34 | 35 | @Test 36 | public void testCallerOfBadOffset() { 37 | assumeJava8(); 38 | StackGetterTestUtil.runTestCallerOfBadOffset(getJavaLangAccessStackGetter()); 39 | } 40 | 41 | private static void assumeJava8() { 42 | assumeTrue(JAVA_SPECIFICATION_VERSION.value().equals("1.8")); 43 | } 44 | 45 | private static StackGetter getJavaLangAccessStackGetter() { 46 | try { 47 | return Class.forName("com.google.common.flogger.util.JavaLangAccessStackGetter") 48 | .asSubclass(StackGetter.class) 49 | .getDeclaredConstructor() 50 | .newInstance(); 51 | } catch (ReflectiveOperationException e) { 52 | throw new LinkageError(e.getMessage(), e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /release/execute-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | readonly MVN_GOAL="$1" 5 | readonly VERSION_NAME="$2" 6 | shift 2 7 | readonly EXTRA_MAVEN_ARGS=("$@") 8 | 9 | bazel_output_file() { 10 | library=$1 11 | library_output=bazel-bin/$library 12 | if [[ ! -e $library_output ]]; then 13 | library_output=bazel-genfiles/$library 14 | fi 15 | if [[ ! -e $library_output ]]; then 16 | echo "Could not find bazel output file for $library" 17 | exit 1 18 | fi 19 | echo -n $library_output 20 | } 21 | 22 | deploy_library() { 23 | library=$1 24 | srcjar=$2 25 | javadoc=$3 26 | pomfile=$4 27 | bazelisk build --define=pom_version="$VERSION_NAME" \ 28 | $library $srcjar $javadoc $pomfile 29 | 30 | mvn $MVN_GOAL \ 31 | -Dfile=$(bazel_output_file $library) \ 32 | -Djavadoc=$(bazel_output_file $javadoc) \ 33 | -DpomFile=$(bazel_output_file $pomfile) \ 34 | -Dsources=$(bazel_output_file $srcjar) \ 35 | "${EXTRA_MAVEN_ARGS[@]:+${EXTRA_MAVEN_ARGS[@]}}" 36 | } 37 | 38 | deploy_library \ 39 | api/merged_api.jar \ 40 | api/libapi-src.jar \ 41 | api/api_javadoc.jar \ 42 | api/api_pom.xml 43 | 44 | deploy_library \ 45 | api/libsystem_backend.jar \ 46 | api/libsystem_backend-src.jar \ 47 | api/system_backend_javadoc.jar \ 48 | api/system_backend_pom.xml 49 | 50 | deploy_library \ 51 | api/libtesting.jar \ 52 | api/libtesting-src.jar \ 53 | api/testing_javadoc.jar \ 54 | api/testing_pom.xml 55 | 56 | deploy_library \ 57 | google/libflogger.jar \ 58 | google/libflogger-src.jar \ 59 | google/flogger_javadoc.jar \ 60 | google/google_logger_pom.xml 61 | 62 | deploy_library \ 63 | log4j2/liblog4j2_backend.jar \ 64 | log4j2/liblog4j2_backend-src.jar \ 65 | log4j2/javadoc.jar \ 66 | log4j2/pom.xml 67 | 68 | deploy_library \ 69 | slf4j/libslf4j_backend.jar \ 70 | slf4j/libslf4j_backend-src.jar \ 71 | slf4j/javadoc.jar \ 72 | slf4j/pom.xml 73 | 74 | deploy_library \ 75 | grpc/libgrpc_context.jar \ 76 | grpc/libgrpc_context-src.jar \ 77 | grpc/javadoc.jar \ 78 | grpc/pom.xml 79 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/testing/FakeLogRecord.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.testing; 18 | 19 | import com.google.common.flogger.LogContext; 20 | import com.google.common.flogger.backend.LogData; 21 | import com.google.common.flogger.backend.Metadata; 22 | import com.google.common.flogger.backend.system.AbstractLogRecord; 23 | 24 | /** 25 | * A fake Flogger based {@link java.util.logging.LogRecord} which extends {@link AbstractLogRecord} 26 | * and can be used to test JDK based log handlers with Flogger behavior. 27 | */ 28 | public final class FakeLogRecord extends AbstractLogRecord { 29 | /** 30 | * Returns a log record for the given log site data. Use {@link FakeLogData} to easily generate 31 | * representative {@code LogData} instances for testing. 32 | * 33 | *

Note that this API doesn't accept additional context metadata since it is always possible to 34 | * just add all necessary metadata to a {@code FakeLogData} instance and pass that in. Tests for 35 | * {@code LogRecord} behavior should never need to differentiate log site vs contextual metadata. 36 | */ 37 | public static FakeLogRecord of(LogData logData) { 38 | return new FakeLogRecord(logData); 39 | } 40 | 41 | private FakeLogRecord(LogData logData) { 42 | super(logData, Metadata.empty()); 43 | setThrown(getMetadataProcessor().getSingleValue(LogContext.Key.LOG_CAUSE)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /slf4j/src/main/java/com/google/common/flogger/backend/slf4j/Slf4jBackendFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.slf4j; 18 | 19 | import com.google.common.flogger.backend.LoggerBackend; 20 | import com.google.common.flogger.backend.system.BackendFactory; 21 | import org.slf4j.LoggerFactory; 22 | 23 | /** 24 | * BackendFactory for SLF4J. 25 | * 26 | *

When using Flogger's {@link com.google.common.flogger.backend.system.DefaultPlatform}, this 27 | * factory will automatically be used if it is included on the classpath and no other implementation 28 | * of {@code BackendFactory} (other than the default implementation) is. To specify it more 29 | * explicitly or to work around an issue where multiple {@code BackendFactory} implementations are 30 | * on the classpath, you can set the {@code flogger.backend_factory} system property: 31 | * 32 | *

    33 | *
  • {@code flogger.backend_factory=com.google.common.flogger.backend.slf4j.Slf4jBackendFactory} 34 | *
35 | */ 36 | public final class Slf4jBackendFactory extends BackendFactory { 37 | 38 | // Must be public for ServiceLoader 39 | public Slf4jBackendFactory() {} 40 | 41 | @Override 42 | public LoggerBackend create(String loggingClassName) { 43 | return new Slf4jLoggerBackend(LoggerFactory.getLogger(loggingClassName.replace('$', '.'))); 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return "SLF4J backend"; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /grpc/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Google Inc. All Rights Reserved. 2 | # Author: dbeaumont@google.com (David Beaumont) 3 | # 4 | # Description: 5 | # Flogger grpc context implementation. 6 | 7 | load("@google_bazel_common//testing:test_defs.bzl", "gen_java_tests") 8 | load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") 9 | load("@rules_java//java:java_library.bzl", "java_library") 10 | load("//tools:maven.bzl", "pom_file") 11 | 12 | GRPC_CONTEXT_SRCS = glob(["src/main/java/**/*.java"]) 13 | 14 | GRPC_CONTEXT_RESOURCES = glob(["src/main/resources/**"]) 15 | 16 | java_library( 17 | name = "grpc_context", 18 | srcs = GRPC_CONTEXT_SRCS, 19 | resources = GRPC_CONTEXT_RESOURCES, 20 | tags = ["maven_coordinates=com.google.flogger:flogger-grpc-context:${project.version}"], 21 | visibility = ["//visibility:public"], 22 | deps = [ 23 | "//api", 24 | "@google_bazel_common//third_party/java/error_prone:annotations", 25 | "@google_bazel_common//third_party/java/grpc:context", 26 | "@google_bazel_common//third_party/java/jspecify_annotations", 27 | ], 28 | ) 29 | 30 | pom_file( 31 | name = "pom", 32 | artifact_id = "flogger-grpc-context", 33 | artifact_name = "Flogger GRPC Context", 34 | targets = [":grpc_context"], 35 | ) 36 | 37 | javadoc_library( 38 | name = "javadoc", 39 | srcs = GRPC_CONTEXT_SRCS, 40 | root_packages = ["com.google.common.flogger.grpc"], 41 | deps = [":grpc_context"], 42 | ) 43 | 44 | # ---- Unit Tests ---- 45 | 46 | gen_java_tests( 47 | name = "grpc_tests", 48 | srcs = glob(["src/test/java/**/*.java"]), 49 | deps = [ 50 | ":grpc_context", 51 | "//api", 52 | "//api:testing", 53 | "@google_bazel_common//third_party/java/auto:service", 54 | "@google_bazel_common//third_party/java/guava", 55 | "@google_bazel_common//third_party/java/guava:testlib", 56 | "@google_bazel_common//third_party/java/junit", 57 | "@google_bazel_common//third_party/java/mockito", 58 | "@google_bazel_common//third_party/java/truth", 59 | ], 60 | ) 61 | -------------------------------------------------------------------------------- /log4j2/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 The Flogger Authors. 2 | # 3 | # Description: 4 | # Flogger log4j2 backend (google.github.io/flogger). 5 | 6 | load("@google_bazel_common//testing:test_defs.bzl", "gen_java_tests") 7 | load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") 8 | load("@rules_java//java:defs.bzl", "java_library") 9 | load("//tools:maven.bzl", "pom_file") 10 | 11 | LOG4J2_BACKEND_SRCS = glob(["src/main/java/**/*.java"]) 12 | 13 | LOG4J2_BACKEND_RESOURCES = glob(["src/main/resources/**"]) 14 | 15 | java_library( 16 | name = "log4j2_backend", 17 | srcs = LOG4J2_BACKEND_SRCS, 18 | resources = LOG4J2_BACKEND_RESOURCES, 19 | tags = ["maven_coordinates=com.google.flogger:flogger-log4j2-backend:${project.version}"], 20 | deps = [ 21 | "//api", 22 | "//api:system_backend", 23 | "@google_bazel_common//third_party/java/error_prone:annotations", 24 | "@google_bazel_common//third_party/java/jspecify_annotations", 25 | "@google_bazel_common//third_party/java/log4j2", 26 | ], 27 | ) 28 | 29 | pom_file( 30 | name = "pom", 31 | artifact_id = "flogger-log4j2-backend", 32 | artifact_name = "Flogger Log4j2 Backend", 33 | targets = [":log4j2_backend"], 34 | ) 35 | 36 | javadoc_library( 37 | name = "javadoc", 38 | srcs = LOG4J2_BACKEND_SRCS, 39 | root_packages = ["com.google.common.flogger.backend.log4j2"], 40 | deps = [":log4j2_backend"], 41 | ) 42 | 43 | # ---- Unit Tests ---- 44 | 45 | gen_java_tests( 46 | name = "log4j2_tests", 47 | srcs = glob(["src/test/java/**/*.java"]), 48 | deps = [ 49 | ":log4j2_backend", 50 | "//api", 51 | "//api:system_backend", 52 | "//api:testing", 53 | "//google:flogger", 54 | "//grpc:grpc_context", 55 | "@google_bazel_common//third_party/java/guava", 56 | "@google_bazel_common//third_party/java/jspecify_annotations", 57 | "@google_bazel_common//third_party/java/junit", 58 | "@google_bazel_common//third_party/java/log4j2", 59 | "@google_bazel_common//third_party/java/truth", 60 | ], 61 | ) 62 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/util/StackGetter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.util; 18 | 19 | /** Interface for finding call site information. */ 20 | interface StackGetter { 21 | /** 22 | * Returns the first caller of a method on the {@code target} class that is *not* a member of 23 | * {@code target} class, by walking back on the stack, or null if the {@code target} class cannot 24 | * be found or is the last element of the stack. 25 | * 26 | * @param target the class to find the caller of 27 | * @param skipFrames skip this many frames before looking for the caller. This can be used for 28 | * optimization. 29 | */ 30 | StackTraceElement callerOf(Class target, int skipFrames); 31 | 32 | /** 33 | * Returns up to {@code maxDepth} frames of the stack starting at the stack frame that is a caller 34 | * of a method on {@code target} class but is *not* itself a method on {@code target} class. 35 | * 36 | * @param target the class to get the stack from 37 | * @param maxDepth the maximum depth of the stack to return. A value of -1 means to return the 38 | * whole stack 39 | * @param skipFrames skip this many stack frames before looking for the target class. Used for 40 | * optimization. 41 | * @throws IllegalArgumentException if {@code maxDepth} is 0 or < -1 or {@code skipFrames} is < 0. 42 | */ 43 | StackTraceElement[] getStackForCaller(Class target, int maxDepth, int skipFrames); 44 | } 45 | -------------------------------------------------------------------------------- /tools/pom-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 4.0.0 19 | 20 | 21 | org.sonatype.oss 22 | oss-parent 23 | 7 24 | 25 | 26 | com.google.flogger 27 | {artifact_id} 28 | {artifact_name} 29 | {pom_version} 30 | A Fluent Logging API for Java 31 | https://github.com/google/flogger 32 | 33 | 34 | https://github.com/google/flogger/ 35 | scm:git:git://github.com/google/flogger.git 36 | scm:git:ssh://git@github.com/google/flogger.git 37 | HEAD 38 | 39 | 40 | 41 | GitHub Issues 42 | https://github.com/google/flogger/issues 43 | 44 | 45 | 46 | 47 | Apache 2.0 48 | https://www.apache.org/licenses/LICENSE-2.0.txt 49 | 50 | 51 | 52 | 53 | Google, Inc. 54 | https://www.google.com 55 | 56 | 57 | 58 | {generated_bzl_deps} 59 | 60 | 61 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/LogSitesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.junit.runners.JUnit4; 24 | 25 | @RunWith(JUnit4.class) 26 | public final class LogSitesTest { 27 | @Test 28 | public void testLogSite() { 29 | assertThat(LogSites.logSite().getMethodName()).isEqualTo("testLogSite"); 30 | } 31 | 32 | @Test 33 | public void testCallerOf() { 34 | assertThat(MyLogUtil.getCallerLogSite().getMethodName()).isEqualTo("testCallerOf"); 35 | assertThat(MyLogUtil.getCallerLogSiteWrapped().getMethodName()).isEqualTo("testCallerOf"); 36 | } 37 | 38 | @Test 39 | public void testCallerOf_notFound() { 40 | assertThat(LogSites.callerOf(String.class)).isEqualTo(LogSite.INVALID); 41 | } 42 | 43 | @Test 44 | public void testCallerOf_from() { 45 | StackTraceElement e = new StackTraceElement("class", "method", "file", 42); 46 | LogSite logSite = LogSites.logSiteFrom(e); 47 | assertThat(logSite.getClassName()).isEqualTo(e.getClassName()); 48 | assertThat(logSite.getMethodName()).isEqualTo(e.getMethodName()); 49 | assertThat(logSite.getFileName()).isEqualTo(e.getFileName()); 50 | assertThat(logSite.getLineNumber()).isEqualTo(e.getLineNumber()); 51 | } 52 | 53 | 54 | private static class MyLogUtil { 55 | static LogSite getCallerLogSite() { 56 | return LogSites.callerOf(MyLogUtil.class); 57 | } 58 | 59 | static LogSite getCallerLogSiteWrapped() { 60 | return getCallerLogSite(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /google/BUILD: -------------------------------------------------------------------------------- 1 | # The GoogleLogger fluent logging library. 2 | # 3 | # This package contains the extended API and classes to implement GoogleLogger 4 | # on top of the core Flogger libraries. The core library is in the 'api' 5 | # directory (which also defines classes in the "com.google.common.flogger" 6 | # package). 7 | # 8 | 9 | load("@google_bazel_common//testing:test_defs.bzl", "gen_java_tests") 10 | load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") 11 | load("@rules_java//java:defs.bzl", "java_library") 12 | load("//tools:maven.bzl", "pom_file") 13 | 14 | GOOGLE_LOGGER_SRCS = glob(["src/main/java/com/google/common/flogger/*.java"]) 15 | 16 | java_library( 17 | name = "flogger", 18 | srcs = GOOGLE_LOGGER_SRCS, 19 | visibility = ["//visibility:public"], 20 | runtime_deps = [ 21 | "//api:system_backend", 22 | ], 23 | deps = [ 24 | "//api", 25 | "@google_bazel_common//third_party/java/error_prone:annotations", 26 | ], 27 | ) 28 | 29 | pom_file( 30 | name = "google_logger_pom", 31 | artifact_id = "google-extensions", 32 | artifact_name = "Google Logger", 33 | targets = [":flogger"], 34 | ) 35 | 36 | javadoc_library( 37 | name = "flogger_javadoc", 38 | srcs = GOOGLE_LOGGER_SRCS, 39 | root_packages = ["com.google.common.flogger"], 40 | deps = [":flogger"], 41 | ) 42 | 43 | filegroup( 44 | name = "javadoc_sources", 45 | srcs = GOOGLE_LOGGER_SRCS, 46 | visibility = ["//visibility:public"], 47 | ) 48 | 49 | # ---- Tests ---- 50 | 51 | gen_java_tests( 52 | name = "google_logger_tests", 53 | srcs = glob(["src/test/java/**/*.java"]), 54 | jvm_flags = ["-Dflogger.logging_context=com.google.common.flogger.grpc.GrpcContextDataProvider#getInstance"], 55 | deps = [ 56 | # Use this for tests which call otherwise prohibited APIs (bypasses errorprone checks). 57 | ":flogger", 58 | "//api", 59 | "//api:testing", 60 | "//grpc:grpc_context", 61 | "@google_bazel_common//third_party/java/guava", 62 | "@google_bazel_common//third_party/java/guava:testlib", 63 | "@google_bazel_common//third_party/java/jspecify_annotations", 64 | "@google_bazel_common//third_party/java/junit", 65 | "@google_bazel_common//third_party/java/truth", 66 | ], 67 | ) 68 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/LogSiteStackTraceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import com.google.common.collect.ImmutableList; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.JUnit4; 25 | 26 | @RunWith(JUnit4.class) 27 | public class LogSiteStackTraceTest { 28 | private static final ImmutableList FAKE_STACK = 29 | ImmutableList.of( 30 | new StackTraceElement("FirstClass", "method1", "Source1.java", 111), 31 | new StackTraceElement("SecondClass", "method2", "Source2.java", 222), 32 | new StackTraceElement("ThirdClass", "method3", "Source3.java", 333)); 33 | 34 | @Test 35 | public void testGetMessage() { 36 | LogSiteStackTrace trace = new LogSiteStackTrace(null, StackSize.FULL, new StackTraceElement[0]); 37 | assertThat(trace).hasMessageThat().isEqualTo("FULL"); 38 | assertThat(trace.getCause()).isNull(); 39 | } 40 | 41 | @Test 42 | public void testGetCause() { 43 | Throwable cause = new RuntimeException(); 44 | LogSiteStackTrace trace = 45 | new LogSiteStackTrace(cause, StackSize.SMALL, new StackTraceElement[0]); 46 | assertThat(trace.getCause()).isSameInstanceAs(cause); 47 | } 48 | 49 | @Test 50 | public void testGetStackTrace() { 51 | StackTraceElement[] stack = FAKE_STACK.toArray(new StackTraceElement[0]); 52 | LogSiteStackTrace trace = new LogSiteStackTrace(null, StackSize.SMALL, stack); 53 | assertThat(trace.getStackTrace()).isNotSameInstanceAs(stack); 54 | assertThat(trace.getStackTrace()).asList().isEqualTo(FAKE_STACK); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /google/src/main/java/com/google/common/flogger/GoogleLogContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import com.google.common.flogger.parser.DefaultPrintfMessageParser; 20 | import com.google.common.flogger.parser.MessageParser; 21 | import java.util.logging.Level; 22 | 23 | /** 24 | * Implementation of any Google specific extensions to the default fluent logging API. This could 25 | * be implemented purely as an inner class inside GoogleLogger, but by making it abstract and top 26 | * level, it allows other teams to subclass it to add additional functionality. 27 | * 28 | * @param The logger implementation from which this context is produced. 29 | * @param The logging api supported by this context. 30 | */ 31 | public abstract class GoogleLogContext< 32 | LOGGER extends AbstractLogger, API extends GoogleLoggingApi> 33 | extends LogContext implements GoogleLoggingApi { 34 | 35 | /** 36 | * Creates a logging context for the GoogleLoggerApi. 37 | * 38 | * @param level the log level of this log statement. 39 | * @param isForced whether the log statement should be forced. 40 | */ 41 | protected GoogleLogContext(Level level, boolean isForced) { 42 | super(level, isForced); 43 | } 44 | 45 | // This is made final to prevent teams within Google switching parsers without contacting the 46 | // Java core libraries team first. In general we expect all instances of GoogleLogger inside 47 | // Google to use the same syntax for place-holders to avoid potential confusion and bugs. 48 | @Override 49 | protected final MessageParser getMessageParser() { 50 | return DefaultPrintfMessageParser.getInstance(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/testing/FakeMetadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.testing; 18 | 19 | import static com.google.common.flogger.util.Checks.checkNotNull; 20 | 21 | import com.google.common.flogger.MetadataKey; 22 | import com.google.common.flogger.backend.Metadata; 23 | import com.google.errorprone.annotations.CanIgnoreReturnValue; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import org.jspecify.annotations.Nullable; 27 | 28 | /** 29 | * A mutable fake {@link Metadata} implementation to help test logging backends and other log 30 | * handling code. 31 | */ 32 | public final class FakeMetadata extends Metadata { 33 | 34 | private static final class KeyValuePair { 35 | private final MetadataKey key; 36 | private final T value; 37 | private KeyValuePair(MetadataKey key, T value) { 38 | this.key = checkNotNull(key, "key"); 39 | this.value = checkNotNull(value, "value"); 40 | } 41 | } 42 | 43 | private final List> entries = new ArrayList<>(); 44 | 45 | @CanIgnoreReturnValue 46 | public FakeMetadata add(MetadataKey key, T value) { 47 | entries.add(new KeyValuePair(key, value)); 48 | return this; 49 | } 50 | 51 | @Override public int size() { 52 | return entries.size(); 53 | } 54 | 55 | @Override public MetadataKey getKey(int n) { 56 | return entries.get(n).key; 57 | } 58 | @Override public Object getValue(int n) { 59 | return entries.get(n).value; 60 | } 61 | 62 | @Override 63 | public @Nullable T findValue(MetadataKey key) { 64 | for (KeyValuePair e : entries) { 65 | if (e.key.equals(key)) { 66 | return key.cast(e.value); 67 | } 68 | } 69 | return null; 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/SpecializedLogSiteKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.flogger.util.Checks.checkNotNull; 20 | 21 | import org.jspecify.annotations.Nullable; 22 | 23 | /** 24 | * Used by Scope/LogSiteMap and in response to "per()" or "perUnique()" (which is an implicitly 25 | * unbounded scope. This should avoid it needing to be made public assuming it's in the same 26 | * package. 27 | */ 28 | final class SpecializedLogSiteKey implements LogSiteKey { 29 | static LogSiteKey of(LogSiteKey key, Object qualifier) { 30 | return new SpecializedLogSiteKey(key, qualifier); 31 | } 32 | 33 | private final LogSiteKey delegate; 34 | private final Object qualifier; 35 | 36 | private SpecializedLogSiteKey(LogSiteKey key, Object qualifier) { 37 | this.delegate = checkNotNull(key, "log site key"); 38 | this.qualifier = checkNotNull(qualifier, "log site qualifier"); 39 | } 40 | 41 | // Equals is dependent on the order in which specialization occurred, even though conceptually it 42 | // needn't be. 43 | @Override 44 | public boolean equals(@Nullable Object obj) { 45 | if (!(obj instanceof SpecializedLogSiteKey)) { 46 | return false; 47 | } 48 | SpecializedLogSiteKey other = (SpecializedLogSiteKey) obj; 49 | return delegate.equals(other.delegate) && qualifier.equals(other.qualifier); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | // Use XOR (which is symmetric) so hash codes are not dependent on specialization order. 55 | return delegate.hashCode() ^ qualifier.hashCode(); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "SpecializedLogSiteKey{ delegate='" + delegate + "', qualifier='" + qualifier + "' }"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/parameter/DateTimeParameter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.parameter; 18 | 19 | import com.google.common.flogger.backend.FormatOptions; 20 | 21 | /** 22 | * A parameter for formatting date/time arguments. 23 | *

24 | * This class is immutable and thread safe, as per the Parameter contract. 25 | */ 26 | public final class DateTimeParameter extends Parameter { 27 | /** 28 | * Returns a {@link Parameter} representing the given formatting options of the specified 29 | * date/time formatting character. Note that a cached value may be returned. 30 | * 31 | * @param format specifier for the specific date/time formatting to be applied. 32 | * @param options the validated formatting options. 33 | * @param index the argument index. 34 | * @return the immutable, thread safe parameter instance. 35 | */ 36 | public static Parameter of(DateTimeFormat format, FormatOptions options, int index) { 37 | return new DateTimeParameter(options, index, format); 38 | } 39 | 40 | private final DateTimeFormat format; 41 | private final String formatString; 42 | 43 | private DateTimeParameter(FormatOptions options, int index, DateTimeFormat format) { 44 | super(options, index); 45 | this.format = format; 46 | this.formatString = 47 | options 48 | .appendPrintfOptions(new StringBuilder("%")) 49 | .append(options.shouldUpperCase() ? 'T' : 't') 50 | .append(format.getChar()) 51 | .toString(); 52 | } 53 | 54 | @Override 55 | protected void accept(ParameterVisitor visitor, Object value) { 56 | visitor.visitDateTime(value, format, getFormatOptions()); 57 | } 58 | 59 | @Override 60 | public String getFormat() { 61 | return formatString; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /google/src/test/java/com/google/common/flogger/PackageSanityTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import com.google.common.base.Predicates; 20 | import com.google.common.collect.ImmutableSet; 21 | import com.google.common.flogger.backend.LoggerBackend; 22 | import com.google.common.flogger.backend.Metadata; 23 | import com.google.common.flogger.testing.FakeLoggerBackend; 24 | import com.google.common.flogger.testing.FakeMetadata; 25 | import com.google.common.testing.AbstractPackageSanityTests; 26 | 27 | /** 28 | * Covers basic sanity checks for the entire package. 29 | * 30 | * @author Kurt Alfred Kluever 31 | */ 32 | @SuppressWarnings("BetaApi") // this is a test, so we can control the version of guava-testlib 33 | public class PackageSanityTest extends AbstractPackageSanityTests { 34 | // Classes which must be ignored (they handle their own nullness check). We _cannot_ use the 35 | // trick of adding a testNulls() method in the tests of classes in other package paths, since 36 | // they may not be built. 37 | private static final ImmutableSet> IGNORE_CLASSES = 38 | ImmutableSet.of(MetadataKey.class); 39 | 40 | public PackageSanityTest() { 41 | // This works around the issue that StackTraceElement has _no_ public constructor, but we need 42 | // to create them for some of the null pointer sanity checks. Any 2 distinct values will do. 43 | StackTraceElement[] stack = new NullPointerException().getStackTrace(); 44 | setDistinctValues(StackTraceElement.class, stack[0], stack[1]); 45 | setDefault(LoggerBackend.class, new FakeLoggerBackend("com.example.NullTester")); 46 | setDefault(MetadataKey.class, new MetadataKey<>("dummy", String.class, false)); 47 | setDefault(Metadata.class, new FakeMetadata()); 48 | ignoreClasses(Predicates.in(IGNORE_CLASSES)); 49 | publicApiOnly(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/TemplateContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend; 18 | 19 | import static com.google.common.flogger.util.Checks.checkNotNull; 20 | 21 | import com.google.common.flogger.parser.MessageParser; 22 | import org.jspecify.annotations.Nullable; 23 | 24 | /** 25 | * A context object for templates that allows caches to validate existing templates or create new 26 | * ones. If two template contexts are equal (via {@link #equals}) then the templates they produce 27 | * are interchangeable. 28 | *

29 | * Template contexts are created by the frontend and passed through to backend implementations via 30 | * the {@link LogData} interface. 31 | */ 32 | public final class TemplateContext { 33 | private final MessageParser parser; 34 | private final String message; 35 | 36 | /** Creates a template context for a log statement. */ 37 | public TemplateContext(MessageParser parser, String message) { 38 | this.parser = checkNotNull(parser, "parser"); 39 | this.message = checkNotNull(message, "message"); 40 | } 41 | 42 | /** Returns the message parser for the log statement. */ 43 | public MessageParser getParser() { 44 | return parser; 45 | } 46 | 47 | /** Returns the message for the log statement. */ 48 | public String getMessage() { 49 | return message; 50 | } 51 | 52 | @Override 53 | public boolean equals(@Nullable Object obj) { 54 | if (obj instanceof TemplateContext) { 55 | TemplateContext other = (TemplateContext) obj; 56 | return parser.equals(other.parser) && message.equals(other.message); 57 | } 58 | return false; 59 | } 60 | 61 | @Override 62 | public int hashCode() { 63 | // We don't expect people to be using the context as a cache key, but it should work. 64 | return parser.hashCode() ^ message.hashCode(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/LogSiteStackTrace.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import org.jspecify.annotations.Nullable; 20 | 21 | /** 22 | * A synthetic exception which can be attached to log statements when additional stack trace 23 | * information is required in log files or via tools such as ECatcher. 24 | *

25 | * The name of this class may become relied upon implicitly by tools such as ECatcher. Do not 26 | * rename or move this class without checking for implicit in logging tools. 27 | */ 28 | public final class LogSiteStackTrace extends Exception { 29 | /** 30 | * Creates a synthetic exception to hold a call-stack generated for the log statement itself. 31 | * 32 | *

This exception is never expected to actually get thrown or caught at any point. 33 | * 34 | * @param cause the optional cause (set via withCause() in the log statement). 35 | * @param stackSize the requested size of the synthetic stack trace (actual trace can be shorter). 36 | * @param syntheticStackTrace the synthetic stack trace starting at the log statement. 37 | */ 38 | LogSiteStackTrace( 39 | @Nullable Throwable cause, StackSize stackSize, StackTraceElement[] syntheticStackTrace) { 40 | super(stackSize.toString(), cause); 41 | // This takes a defensive copy, but there's no way around that. Note that we cannot override 42 | // getStackTrace() to avoid a defensive copy because that breaks stack trace formatting 43 | // (which doesn't call getStackTrace() directly). See b/27310448. 44 | setStackTrace(syntheticStackTrace); 45 | } 46 | 47 | // We override this because it gets called from the superclass constructor and we don't want 48 | // it to do any work (we always replace it immediately). 49 | @SuppressWarnings("UnsynchronizedOverridesSynchronized") 50 | @Override 51 | public Throwable fillInStackTrace() { 52 | return this; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/parser/DefaultBraceStyleMessageParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.parser; 18 | 19 | import com.google.common.flogger.parameter.BraceStyleParameter; 20 | 21 | /** 22 | * Default implementation of the brace style message parser. Note that while the underlying parsing 23 | * mechanism supports the more general "{n,xxx}" form for brace format style logging, the default 24 | * message parser is currently limited to simple indexed place holders (e.g. "{0}"). This class 25 | * could easily be extended to support these trailing format specifiers. 26 | *

27 | * Note also that the implicit place holder syntax used by Log4J (i.e. "{}") is not currently 28 | * supported, however this may change. Currently an unescaped "{}" term in a log message will cause 29 | * a parse error, so adding support for it should not be an issue. 30 | */ 31 | public class DefaultBraceStyleMessageParser extends BraceStyleMessageParser { 32 | private static final BraceStyleMessageParser INSTANCE = new DefaultBraceStyleMessageParser(); 33 | 34 | public static BraceStyleMessageParser getInstance() { 35 | return INSTANCE; 36 | } 37 | 38 | private DefaultBraceStyleMessageParser() {} 39 | 40 | @Override 41 | public void parseBraceFormatTerm( 42 | MessageBuilder builder, 43 | int index, 44 | String message, 45 | int termStart, 46 | int formatStart, 47 | int termEnd) 48 | throws ParseException { 49 | 50 | if (formatStart != -1) { 51 | // Specify the optional trailing part including leading ':' but excluding trailing '}'. 52 | throw ParseException.withBounds( 53 | "the default brace style parser does not allow trailing format specifiers", 54 | message, 55 | formatStart - 1, 56 | termEnd - 1); 57 | } 58 | builder.addParameter(termStart, termEnd, BraceStyleParameter.of(index)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /log4j2/src/main/java/com/google/common/flogger/backend/log4j2/Log4j2LoggerBackend.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.log4j2; 18 | 19 | import static com.google.common.flogger.backend.log4j2.Log4j2LogEventUtil.toLog4jLevel; 20 | import static com.google.common.flogger.backend.log4j2.Log4j2LogEventUtil.toLog4jLogEvent; 21 | 22 | import com.google.common.flogger.backend.LogData; 23 | import com.google.common.flogger.backend.LoggerBackend; 24 | import org.apache.logging.log4j.core.Logger; 25 | 26 | /** 27 | * A logging backend that uses log4j2 to output log statements. 28 | * 29 | *

Note: Any changes in this code should, as far as possible, be reflected in the equivalently 30 | * named log4j implementation. If the behaviour of this class starts to deviate from that of the 31 | * log4j backend in any significant way, this difference should be called out clearly in the 32 | * documentation. 33 | */ 34 | final class Log4j2LoggerBackend extends LoggerBackend { 35 | private final Logger logger; 36 | 37 | // VisibleForTesting 38 | Log4j2LoggerBackend(Logger logger) { 39 | this.logger = logger; 40 | } 41 | 42 | @Override 43 | public String getLoggerName() { 44 | // Logger#getName() returns exactly the name that we used to create the Logger in 45 | // Log4jBackendFactory. 46 | return logger.getName(); 47 | } 48 | 49 | @Override 50 | public boolean isLoggable(java.util.logging.Level level) { 51 | return logger.isEnabled(toLog4jLevel(level)); 52 | } 53 | 54 | @Override 55 | public void log(LogData logData) { 56 | // The caller is responsible to call isLoggable() before calling this method to ensure that only 57 | // messages above the given threshold are logged. 58 | logger.get().log(toLog4jLogEvent(logger.getName(), logData)); 59 | } 60 | 61 | @Override 62 | public void handleError(RuntimeException error, LogData badData) { 63 | logger.get().log(toLog4jLogEvent(logger.getName(), error, badData)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/system/LoggingContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.system; 18 | 19 | import com.google.common.flogger.context.ContextDataProvider; 20 | import com.google.common.flogger.context.LogLevelMap; 21 | import com.google.common.flogger.context.ScopeType; 22 | import com.google.common.flogger.context.ScopedLoggingContext; 23 | import com.google.common.flogger.context.ScopedLoggingContext.LoggingContextCloseable; 24 | import com.google.common.flogger.context.Tags; 25 | /** 26 | * @deprecated Replaced by ContextDataProvider. 27 | */ 28 | // TODO(b/173778154): Delete this class once nothing external relies on it. 29 | @Deprecated 30 | public abstract class LoggingContext extends ContextDataProvider { 31 | // Needed temporarily while old LoggingContext based implementations are migrated away from. 32 | private static final ScopedLoggingContext NO_OP_API = new NoOpScopedLoggingContext(); 33 | 34 | @Override 35 | public ScopedLoggingContext getContextApiSingleton() { 36 | return NO_OP_API; 37 | } 38 | 39 | private static final class NoOpScopedLoggingContext extends ScopedLoggingContext 40 | implements LoggingContextCloseable { 41 | @Override 42 | public ScopedLoggingContext.Builder newContext() { 43 | return new ScopedLoggingContext.Builder() { 44 | @Override 45 | public LoggingContextCloseable install() { 46 | return NoOpScopedLoggingContext.this; 47 | } 48 | }; 49 | } 50 | 51 | @Override 52 | public Builder newContext(ScopeType scopeType) { 53 | // Scopes unsupported in the old LoggingContext based implementations. 54 | return newContext(); 55 | } 56 | 57 | @Override 58 | public void close() {} 59 | 60 | @Override 61 | public boolean addTags(Tags tags) { 62 | return false; 63 | } 64 | 65 | @Override 66 | public boolean applyLogLevelMap(LogLevelMap m) { 67 | return false; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/LogSiteFormatters.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend; 18 | 19 | import com.google.common.flogger.LogSite; 20 | 21 | /** Sample LogSiteFormatter implementations. */ 22 | public enum LogSiteFormatters implements LogSiteFormatter { 23 | DEFAULT { 24 | /** Appends logsite information in the default format, .: */ 25 | @Override 26 | public boolean appendLogSite(LogSite logSite, StringBuilder out) { 27 | if (logSite == LogSite.INVALID) { 28 | return false; 29 | } 30 | out.append(logSite.getClassName()) 31 | .append('.') 32 | .append(logSite.getMethodName()) 33 | .append(':') 34 | .append(logSite.getLineNumber()); 35 | return true; 36 | } 37 | }, 38 | NO_OP { 39 | /** Does not append logsite information. */ 40 | @Override 41 | public boolean appendLogSite(LogSite logSite, StringBuilder out) { 42 | return false; 43 | } 44 | }, 45 | SIMPLE_CLASSNAME { 46 | /** 47 | * Appends logsite information using the unqualified class name, 48 | * .: 49 | * 50 | *

The unqualified class name is all text after the last period. A class name with no 51 | * separators or a trailing separator will be appended in full. 52 | */ 53 | @Override 54 | public boolean appendLogSite(LogSite logSite, StringBuilder out) { 55 | if (logSite == LogSite.INVALID) { 56 | return false; 57 | } 58 | String qualifiedClassName = logSite.getClassName(); 59 | int lastDotIndex = qualifiedClassName.lastIndexOf('.'); 60 | if (lastDotIndex == -1 || lastDotIndex + 1 >= qualifiedClassName.length()) { 61 | out.append(qualifiedClassName); 62 | } else { 63 | out.append(qualifiedClassName, lastDotIndex + 1, qualifiedClassName.length()); 64 | } 65 | out.append('.').append(logSite.getMethodName()).append(':').append(logSite.getLineNumber()); 66 | return true; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/parser/MessageParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.parser; 18 | 19 | /** 20 | * Base class from which any specific message parsers are derived (e.g. {@link PrintfMessageParser} 21 | * and {@link BraceStyleMessageParser}). 22 | */ 23 | public abstract class MessageParser { 24 | /** 25 | * The maximum allowed index (this should correspond to the MAX_ALLOWED_WIDTH in 26 | * {@link com.google.common.flogger.backend.FormatOptions FormatOptions} because at times it is 27 | * ambiguous as to which is being parsed). 28 | */ 29 | public static final int MAX_ARG_COUNT = 1000000; 30 | 31 | /** 32 | * Abstract parse method implemented by specific subclasses to modify parsing behavior. 33 | *

34 | * Note that when extending parsing behavior, it is expected that specific parsers such as 35 | * {@link DefaultPrintfMessageParser} or {@link DefaultBraceStyleMessageParser} will be 36 | * sub-classed. Extending this class directly is only necessary when an entirely new type of 37 | * format needs to be supported (which should be extremely rare). 38 | *

39 | * Implementations of this method are required to invoke the 40 | * {@link MessageBuilder#addParameterImpl} method of the supplied builder once for each 41 | * parameter place-holder in the message. 42 | */ 43 | protected abstract void parseImpl(MessageBuilder builder) throws ParseException; 44 | 45 | /** 46 | * Appends the unescaped literal representation of the given message string (assumed to be escaped 47 | * according to this parser's escaping rules). This method is designed to be invoked from a 48 | * callback method in a {@link MessageBuilder} instance. 49 | * 50 | * @param out the destination into which to append characters 51 | * @param message the escaped log message 52 | * @param start the start index (inclusive) in the log message 53 | * @param end the end index (exclusive) in the log message 54 | */ 55 | public abstract void unescape(StringBuilder out, String message, int start, int end); 56 | } 57 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/testing/FormatTypeSubject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.testing; 18 | 19 | import static com.google.common.truth.Truth.assertAbout; 20 | import static com.google.common.truth.Truth.assertWithMessage; 21 | 22 | import com.google.common.flogger.backend.FormatType; 23 | import com.google.common.truth.FailureMetadata; 24 | import com.google.common.truth.Subject; 25 | import org.jspecify.annotations.Nullable; 26 | 27 | /** 28 | * A Truth subject for {@link FormatType}. 29 | * 30 | * @author Kurt Alfred Kluever (kak@google.com) 31 | */ 32 | public final class FormatTypeSubject extends Subject { 33 | 34 | public static FormatTypeSubject assertThat(@Nullable FormatType formatType) { 35 | return assertAbout(FormatTypeSubject.FORMAT_TYPE_SUBJECT_FACTORY).that(formatType); 36 | } 37 | 38 | private static final Subject.Factory FORMAT_TYPE_SUBJECT_FACTORY = 39 | FormatTypeSubject::new; 40 | 41 | private final FormatType actual; 42 | 43 | private FormatTypeSubject(FailureMetadata failureMetadata, @Nullable FormatType subject) { 44 | super(failureMetadata, subject); 45 | this.actual = subject; 46 | } 47 | 48 | public void canFormat(Object arg) { 49 | assertWithMessage("Unable to format " + arg + " using " + actual) 50 | .that(actual.canFormat(arg)) 51 | .isTrue(); 52 | } 53 | 54 | public void cannotFormat(Object arg) { 55 | assertWithMessage("Expected error when formatting " + arg + " using " + actual) 56 | .that(actual.canFormat(arg)) 57 | .isFalse(); 58 | } 59 | 60 | public void isNumeric() { 61 | check("isNumeric()") 62 | .withMessage("Expected " + actual + " to be numeric but wasn't") 63 | .that(actual.isNumeric()) 64 | .isTrue(); 65 | } 66 | 67 | public void isNotNumeric() { 68 | check("isNumeric()") 69 | .withMessage("Expected " + actual + " to not be numeric but was") 70 | .that(actual.isNumeric()) 71 | .isFalse(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/parameter/ParameterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.parameter; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | import static org.junit.Assert.fail; 21 | 22 | import com.google.common.flogger.backend.FormatOptions; 23 | import com.google.common.flogger.parser.ParseException; 24 | import org.jspecify.annotations.Nullable; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.junit.runners.JUnit4; 28 | 29 | @RunWith(JUnit4.class) 30 | public class ParameterTest { 31 | // We just need a concrete class here. 32 | private static class TestParameter extends Parameter { 33 | public TestParameter(@Nullable FormatOptions options, int index) { 34 | super(options, index); 35 | } 36 | 37 | @Override 38 | protected void accept(ParameterVisitor visitor, Object value) { 39 | throw new UnsupportedOperationException(); 40 | } 41 | 42 | @Override 43 | public String getFormat() { 44 | throw new UnsupportedOperationException(); 45 | } 46 | } 47 | 48 | @Test 49 | public void testBadArgs() { 50 | FormatOptions options = FormatOptions.getDefault(); 51 | try { 52 | new TestParameter(null, 0); 53 | fail("expected IllegalArgumentException"); 54 | } catch (IllegalArgumentException expected) { 55 | } 56 | try { 57 | new TestParameter(options, -1); 58 | fail("expected IllegalArgumentException"); 59 | } catch (IllegalArgumentException expected) { 60 | } 61 | } 62 | 63 | @Test 64 | public void testGetOptions() throws ParseException { 65 | FormatOptions options = FormatOptions.parse("-2.2", 0, 4, false); 66 | Parameter p = new TestParameter(options, 0); 67 | assertThat(p.getFormatOptions()).isSameInstanceAs(options); 68 | } 69 | 70 | @Test 71 | public void testSingleArgumentParameter() { 72 | Parameter p0 = new TestParameter(FormatOptions.getDefault(), 0); 73 | Parameter p1 = new TestParameter(FormatOptions.getDefault(), 1); 74 | 75 | assertThat(p0.getIndex()).isEqualTo(0); 76 | assertThat(p1.getIndex()).isEqualTo(1); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/LogMessageFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend; 18 | 19 | import com.google.errorprone.annotations.CanIgnoreReturnValue; 20 | 21 | /** 22 | * API for formatting Flogger log messages from logData and scoped metadata. 23 | * 24 | *

This API is not used directly in the core Flogger libraries yet, but will become part of the 25 | * log message formatting API eventually. For now it should be considered an implementation detail 26 | * and definitely unstable. 27 | */ 28 | // TODO(dbeaumont): This needs to either move into "system" or be extended somehow. 29 | // This is currently tightly coupled with the JDK log handler behaviour (by virtue of what data is 30 | // expected to be used for formatting) so it is not suitable as a general purpose API yet. 31 | public abstract class LogMessageFormatter { 32 | /** 33 | * Returns a formatted representation of the log message and metadata. Currently this class is 34 | * only responsible for formatting the main body of the log message and not thing like log site, 35 | * timestamps or thread information. 36 | * 37 | *

By default this method just returns: 38 | * 39 | *

{@code append(logData, metadata, new StringBuilder()).toString()}
40 | * 41 | *

Formatter implementations may be able to implement it more efficiently (e.g. if they can 42 | * safely detect when no formatting is required). See also the helper methods in {@link 43 | * SimpleMessageFormatter}. 44 | */ 45 | public String format(LogData logData, MetadataProcessor metadata) { 46 | return append(logData, metadata, new StringBuilder()).toString(); 47 | } 48 | 49 | /** 50 | * Formats the log message and metadata into the given buffer. Currently this class is only 51 | * responsible for formatting the main body of the log message and not thing like log site, 52 | * timestamps or thread information. 53 | * 54 | * @return the given buffer for method chaining. 55 | */ 56 | @CanIgnoreReturnValue 57 | public abstract StringBuilder append( 58 | LogData logData, MetadataProcessor metadata, StringBuilder buffer); 59 | } 60 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/util/ThrowableStackGetter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.util; 18 | 19 | import static com.google.common.flogger.util.Checks.checkArgument; 20 | 21 | import org.jspecify.annotations.Nullable; 22 | 23 | /** Default implementation of {@link StackGetter} using {@link Throwable#getStackTrace}. */ 24 | final class ThrowableStackGetter implements StackGetter { 25 | 26 | @Override 27 | public @Nullable StackTraceElement callerOf(Class target, int skipFrames) { 28 | checkArgument(skipFrames >= 0, "skipFrames must be >= 0"); 29 | StackTraceElement[] stack = new Throwable().getStackTrace(); 30 | int callerIndex = findCallerIndex(stack, target, skipFrames + 1); 31 | if (callerIndex != -1) { 32 | return stack[callerIndex]; 33 | } 34 | 35 | return null; 36 | } 37 | 38 | @Override 39 | public StackTraceElement[] getStackForCaller(Class target, int maxDepth, int skipFrames) { 40 | checkArgument(maxDepth == -1 || maxDepth > 0, "maxDepth must be > 0 or -1"); 41 | checkArgument(skipFrames >= 0, "skipFrames must be >= 0"); 42 | StackTraceElement[] stack = new Throwable().getStackTrace(); 43 | int callerIndex = findCallerIndex(stack, target, skipFrames + 1); 44 | if (callerIndex == -1) { 45 | return new StackTraceElement[0]; 46 | } 47 | int elementsToAdd = stack.length - callerIndex; 48 | if (maxDepth > 0 && maxDepth < elementsToAdd) { 49 | elementsToAdd = maxDepth; 50 | } 51 | StackTraceElement[] stackTrace = new StackTraceElement[elementsToAdd]; 52 | System.arraycopy(stack, callerIndex, stackTrace, 0, elementsToAdd); 53 | return stackTrace; 54 | } 55 | 56 | private int findCallerIndex(StackTraceElement[] stack, Class target, int skipFrames) { 57 | boolean foundCaller = false; 58 | String targetClassName = target.getName(); 59 | for (int frameIndex = skipFrames; frameIndex < stack.length; frameIndex++) { 60 | if (stack[frameIndex].getClassName().equals(targetClassName)) { 61 | foundCaller = true; 62 | } else if (foundCaller) { 63 | return frameIndex; 64 | } 65 | } 66 | return -1; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/LazyArgs.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.flogger.util.Checks.checkNotNull; 20 | 21 | /** 22 | * Static utility methods for lazy argument evaluation in Flogger. The {@link #lazy(LazyArg)} 23 | * method allows lambda expressions to be "cast" to the {@link LazyArg} interface. 24 | * 25 | *

In cases where the log statement is strongly expected to always be enabled (e.g. unconditional 26 | * logging at warning or above) it may not be worth using lazy evaluation because any work required 27 | * to evaluate arguments will happen anyway. 28 | * 29 | *

If lambdas are available, users should prefer using this class rather than explicitly creating 30 | * {@code LazyArg} instances. 31 | */ 32 | // TODO: Add other generally useful methods here, especially things which help non-lambda users. 33 | public final class LazyArgs { 34 | /** 35 | * Coerces a lambda expression or method reference to return a lazily evaluated logging argument. 36 | * Pass in a compatible, no-argument, lambda expression or method reference to have it evaluated 37 | * only when logging will actually occur. 38 | * 39 | *

{@code
40 |    * logger.atFine().log("value=%s", lazy(() -> doExpensive()));
41 |    * logger.atWarning().atMostEvery(5, MINUTES).log("value=%s", lazy(stats::create));
42 |    * }
43 | * 44 | * Evaluation of lazy arguments occurs at most once, and always in the same thread from which the 45 | * logging call was made. 46 | * 47 | *

Note also that it is almost never suitable to make a {@code toString()} call "lazy" using 48 | * this mechanism and, in general, explicitly calling {@code toString()} on arguments which are 49 | * being logged is an error as it precludes the ability to log an argument structurally. 50 | */ 51 | public static LazyArg lazy(LazyArg lambdaOrMethodReference) { 52 | // This method is essentially a coercing cast for the functional interface to give the compiler 53 | // a target type to convert a lambda expression or method reference into. 54 | return checkNotNull(lambdaOrMethodReference, "lazy arg"); 55 | } 56 | 57 | private LazyArgs() {} 58 | } 59 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/backend/system/DefaultPlatformServiceLoadingTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.system; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import com.google.auto.service.AutoService; 22 | import com.google.common.flogger.backend.LoggerBackend; 23 | import com.google.common.flogger.context.ContextDataProvider; 24 | import com.google.common.flogger.context.ScopedLoggingContext; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.junit.runners.JUnit4; 28 | 29 | /** Tests how {@code DefaultPlatform} loads services from the classpath. */ 30 | @RunWith(JUnit4.class) 31 | public final class DefaultPlatformServiceLoadingTest { 32 | 33 | @Test 34 | public void testConfigString() { 35 | DefaultPlatform platform = new DefaultPlatform(); 36 | assertThat(platform.getConfigInfoImpl()).contains(DefaultPlatform.class.getName()); 37 | assertThat(platform.getConfigInfoImpl()).contains("Clock: Default millisecond precision clock"); 38 | assertThat(platform.getConfigInfoImpl()).contains("BackendFactory: TestBackendFactoryService"); 39 | assertThat(platform.getConfigInfoImpl()) 40 | .contains("ContextDataProvider: TestContextDataProviderService"); 41 | assertThat(platform.getConfigInfoImpl()) 42 | .contains("LogCallerFinder: Default stack-based caller finder"); 43 | } 44 | 45 | @AutoService(BackendFactory.class) 46 | public static final class TestBackendFactoryService extends BackendFactory { 47 | @Override 48 | public LoggerBackend create(String loggingClassName) { 49 | throw new UnsupportedOperationException(); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "TestBackendFactoryService"; 55 | } 56 | } 57 | 58 | @AutoService(ContextDataProvider.class) 59 | public static final class TestContextDataProviderService extends ContextDataProvider { 60 | @Override 61 | public ScopedLoggingContext getContextApiSingleton() { 62 | throw new UnsupportedOperationException(); 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "TestContextDataProviderService"; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/SpecializedLogSiteKeyTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import com.google.common.flogger.testing.FakeLogSite; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.JUnit4; 25 | 26 | @RunWith(JUnit4.class) 27 | public class SpecializedLogSiteKeyTest { 28 | 29 | @Test 30 | public void testEqualsAndHashCode() { 31 | LogSite logSite = FakeLogSite.create("com.google.foo.Foo", "doFoo", 42, ""); 32 | LogSiteKey fooKey = SpecializedLogSiteKey.of(logSite, "foo"); 33 | 34 | assertThat(SpecializedLogSiteKey.of(logSite, "foo")).isEqualTo(fooKey); 35 | assertThat(SpecializedLogSiteKey.of(logSite, "foo").hashCode()).isEqualTo(fooKey.hashCode()); 36 | 37 | assertThat(SpecializedLogSiteKey.of(logSite, "bar")).isNotEqualTo(fooKey); 38 | assertThat(SpecializedLogSiteKey.of(logSite, "bar").hashCode()).isNotEqualTo(fooKey.hashCode()); 39 | 40 | LogSite otherLogSite = FakeLogSite.create("com.google.foo.Bar", "doOther", 23, ""); 41 | assertThat(SpecializedLogSiteKey.of(otherLogSite, "foo")).isNotEqualTo(fooKey); 42 | assertThat(SpecializedLogSiteKey.of(otherLogSite, "foo").hashCode()) 43 | .isNotEqualTo(fooKey.hashCode()); 44 | } 45 | 46 | // Conceptually order does not matter, but it is hard to make equals work efficiently and be 47 | // order invariant. However having two or more specializations on a key will almost never happen, 48 | // and even if it does, the metadata preserves order at the log site, so keys should be the same 49 | // each time. 50 | // TODO: Consider making equality invariant to specialization order if it can be done efficiently. 51 | @Test 52 | public void testSpecializationOrderMatters() { 53 | LogSite logSite = FakeLogSite.create("com.google.foo.Foo", "doFoo", 42, ""); 54 | LogSiteKey fooBarKey = 55 | SpecializedLogSiteKey.of(SpecializedLogSiteKey.of(logSite, "foo"), "bar"); 56 | LogSiteKey barFooKey = 57 | SpecializedLogSiteKey.of(SpecializedLogSiteKey.of(logSite, "bar"), "foo"); 58 | assertThat(fooBarKey).isNotEqualTo(barFooKey); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/parameter/Parameter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.parameter; 18 | 19 | import com.google.common.flogger.backend.FormatOptions; 20 | 21 | /** 22 | * An abstract representation of a parameter for a message template. 23 | *

24 | * Note that this is implemented as a class (rather than via an interface) because it is very 25 | * helpful to have explicit checks for the index values and count to ensure we can calculate 26 | * reliable low bounds for the number of arguments a template can accept. 27 | *

28 | * Note that all subclasses of Parameter must be immutable and thread safe. 29 | */ 30 | public abstract class Parameter { 31 | private final int index; 32 | private final FormatOptions options; 33 | 34 | /** 35 | * Constructs a parameter to format an argument using specified formatting options. 36 | * 37 | * @param options the format options for this parameter. 38 | * @param index the index of the argument processed by this parameter. 39 | */ 40 | protected Parameter(FormatOptions options, int index) { 41 | if (options == null) { 42 | throw new IllegalArgumentException("format options cannot be null"); 43 | } 44 | if (index < 0) { 45 | throw new IllegalArgumentException("invalid index: " + index); 46 | } 47 | this.index = index; 48 | this.options = options; 49 | } 50 | 51 | /** Returns the index of the argument to be processed by this parameter. */ 52 | public final int getIndex() { 53 | return index; 54 | } 55 | 56 | /** Returns the formatting options. */ 57 | protected final FormatOptions getFormatOptions() { 58 | return options; 59 | } 60 | 61 | public final void accept(ParameterVisitor visitor, Object[] args) { 62 | if (getIndex() < args.length) { 63 | Object value = args[getIndex()]; 64 | if (value != null) { 65 | accept(visitor, value); 66 | } else { 67 | visitor.visitNull(); 68 | } 69 | } else { 70 | visitor.visitMissing(); 71 | } 72 | } 73 | 74 | protected abstract void accept(ParameterVisitor visitor, Object value); 75 | 76 | /** 77 | * Returns the printf format string specified for this parameter (eg, "%d" or "%tc"). 78 | */ 79 | public abstract String getFormat(); 80 | } 81 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/system/StackBasedCallerFinder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.system; 18 | 19 | import com.google.common.flogger.AbstractLogger; 20 | import com.google.common.flogger.LogSite; 21 | import com.google.common.flogger.LogSites; 22 | import com.google.common.flogger.backend.Platform.LogCallerFinder; 23 | import com.google.common.flogger.util.CallerFinder; 24 | 25 | /** 26 | * Default caller finder implementation which should work on all recent Java releases. 27 | * 28 | *

See class documentation in {@link LogCallerFinder} for important implementation restrictions. 29 | */ 30 | public final class StackBasedCallerFinder extends LogCallerFinder { 31 | private static final LogCallerFinder INSTANCE = new StackBasedCallerFinder(); 32 | 33 | // Called during logging platform initialization; MUST NOT call any code that might log. 34 | public static LogCallerFinder getInstance() { 35 | return INSTANCE; 36 | } 37 | 38 | @Override 39 | public String findLoggingClass(Class> loggerClass) { 40 | // We can skip at most only 1 method from the analysis, the inferLoggingClass() method itself. 41 | StackTraceElement caller = CallerFinder.findCallerOf(loggerClass, 1); 42 | if (caller != null) { 43 | // This might contain '$' for inner/nested classes, but that's okay. 44 | return caller.getClassName(); 45 | } 46 | throw new IllegalStateException("no caller found on the stack for: " + loggerClass.getName()); 47 | } 48 | 49 | @Override 50 | public LogSite findLogSite(Class loggerApi, int stackFramesToSkip) { 51 | // Skip an additional stack frame because we create the Throwable inside this method, not at 52 | // the point that this method was invoked (which allows completely alternate implementations 53 | // to avoid even constructing the Throwable instance). 54 | StackTraceElement caller = CallerFinder.findCallerOf(loggerApi, stackFramesToSkip + 1); 55 | // Returns INVALID if "caller" is null (no caller found for given API class). 56 | return LogSites.logSiteFrom(caller); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "Default stack-based caller finder"; 62 | } 63 | 64 | private StackBasedCallerFinder() {} 65 | } 66 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are a 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to any Google project must be accompanied by a Contributor License 9 | Agreement. This is not a copyright **assignment**, it simply gives Google 10 | permission to use and redistribute your contributions as part of the project. 11 | 12 | When submitting a pull request, if you have not already signed the 13 | [Contributor License Agreement (CLA)][CLA], then a bot will remind you. 14 | Code cannot even be evaluated without this step. 15 | 16 | ## Submitting a patch 17 | 18 | 1. It's generally best to start by opening a new issue describing the bug or 19 | feature you're intending to fix. Even if you think it's relatively minor, 20 | it's helpful to know what people are working on. Mention in the initial 21 | issue that you are planning to work on that bug or feature so that it can be 22 | assigned to you. 23 | 24 | 2. Follow the normal process of [forking] the project, and setup a new branch 25 | to work in. It's important that each group of changes be done in separate 26 | branches in order to ensure that a pull request only includes the commits 27 | related to that bug or feature. 28 | 29 | 3. Any significant changes should almost always be accompanied by tests. The 30 | project already has good test coverage, so look at some of the existing 31 | tests if you're unsure how to go about it. 32 | 33 | 4. All contributions must be licensed Apache 2.0 and all files must have a copy 34 | of the boilerplate licence comment (can be copied from an existing file). 35 | Files should be formatted according to Google's [java style guide]. 36 | 37 | 5. Do your best to have [well-formed commit messages] for each change. This 38 | provides consistency throughout the project, and ensures that commit 39 | messages are able to be formatted properly by various git tools. 40 | 41 | 6. Finally, push the commits to your fork and submit a [pull request]. 42 | 43 | ### Merging pull requests 44 | 45 | Due to Flogger's nature as a subset of Google's internal codebase which is 46 | automatically synced to GitHub, we are unable to merge pull requests directly 47 | into the master branch. Instead, once a pull request is ready for merging, we'll 48 | make the appropriate changes in the internal codebase and sync that internal 49 | change out using our regular process. The synced commit will reflect the pull 50 | request author as the author of the commit. 51 | 52 | [CLA]: https://cla.developers.google.com 53 | [forking]: https://help.github.com/articles/fork-a-repo 54 | [java style guide]: https://google.github.io/styleguide/javaguide.html 55 | [well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 56 | [pull request]: https://help.github.com/articles/creating-a-pull-request 57 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/util/StackGetterTestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.util; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import com.google.common.flogger.util.StackGetterTestUtil.LoggerCode; 22 | import org.jspecify.annotations.Nullable; 23 | 24 | final class StackGetterTestUtil { 25 | 26 | private StackGetterTestUtil() {} 27 | 28 | /** Fake class that emulates some code calling a log method. */ 29 | static class UserCode { 30 | final LoggerCode logger; 31 | 32 | UserCode(LoggerCode library) { 33 | this.logger = library; 34 | } 35 | 36 | void invokeUserCode() { 37 | loggingMethod(); 38 | } 39 | 40 | void loggingMethod() { 41 | logger.logMethod(); 42 | } 43 | } 44 | 45 | /** Fake class that emulates the logging library which eventually calls 'findCallerOf()'. */ 46 | static class LoggerCode { 47 | final int skipCount; 48 | final StackGetter stackGetter; 49 | @Nullable StackTraceElement caller = null; 50 | 51 | public LoggerCode(int skipCount, StackGetter stackGetter) { 52 | this.skipCount = skipCount; 53 | this.stackGetter = stackGetter; 54 | } 55 | 56 | void logMethod() { 57 | internalMethodOne(); 58 | } 59 | 60 | void internalMethodOne() { 61 | internalMethodTwo(); 62 | } 63 | 64 | void internalMethodTwo() { 65 | caller = stackGetter.callerOf(LoggerCode.class, skipCount); 66 | } 67 | } 68 | 69 | static void runTestCallerOf(StackGetter stackGetter) { 70 | // There are 2 internal methods (not including the log method itself) in our fake library. 71 | LoggerCode library = new LoggerCode(2, stackGetter); 72 | UserCode code = new UserCode(library); 73 | code.invokeUserCode(); 74 | assertThat(library.caller.getClassName()).isEqualTo(UserCode.class.getName()); 75 | assertThat(library.caller.getMethodName()).isEqualTo("loggingMethod"); 76 | } 77 | 78 | static void runTestCallerOfBadOffset(StackGetter stackGetter) { 79 | // If the minimum offset exceeds the number of internal methods, the find fails. 80 | LoggerCode library = new LoggerCode(3, stackGetter); 81 | UserCode code = new UserCode(library); 82 | code.invokeUserCode(); 83 | assertThat(library.caller).isNull(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /log4j2/src/main/java/com/google/common/flogger/backend/log4j2/Log4j2BackendFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.log4j2; 18 | 19 | import com.google.common.flogger.backend.LoggerBackend; 20 | import com.google.common.flogger.backend.system.BackendFactory; 21 | import org.apache.logging.log4j.LogManager; 22 | import org.apache.logging.log4j.core.Logger; 23 | 24 | /** 25 | * BackendFactory for log4j2. 26 | * 27 | *

When using Flogger's {@link com.google.common.flogger.backend.system.DefaultPlatform}, this 28 | * factory will automatically be used if it is included on the classpath and no other implementation 29 | * of {@code BackendFactory} (other than the default implementation) is. To specify it more 30 | * explicitly or to work around an issue where multiple {@code BackendFactory} implementations are 31 | * on the classpath, you can set the {@code flogger.backend_factory} system property: 32 | * 33 | *

    34 | *
  • {@code 35 | * flogger.backend_factory=com.google.common.flogger.backend.log4j2.Log4j2BackendFactory} 36 | *
37 | * 38 | *

Note: Any changes in this code should, as far as possible, be reflected in the equivalently 39 | * named log4j implementation. If the behaviour of this class starts to deviate from that of the 40 | * log4j backend in any significant way, this difference should be called out clearly in the 41 | * documentation. 42 | */ 43 | public final class Log4j2BackendFactory extends BackendFactory { 44 | 45 | // Must be public for ServiceLoader 46 | public Log4j2BackendFactory() {} 47 | 48 | @Override 49 | public LoggerBackend create(String loggingClassName) { 50 | // Compute the logger name exactly the same way as in SimpleBackendFactory. 51 | // The logger name must match the name of the logging class so that we can return it from 52 | // Log4j2LoggerBackend#getLoggerName(). 53 | // We cast org.apache.logging.log4j.core.Logger here so that 54 | // we can access the methods only avilable under org.apache.logging.log4j.core.Logger. 55 | // TODO(b/27920233): Strip inner/nested classes when deriving logger name. 56 | Logger logger = (Logger) LogManager.getLogger(loggingClassName.replace('$', '.')); 57 | return new Log4j2LoggerBackend(logger); 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "Log4j2 backend"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/testing/FakeLogSite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.testing; 18 | 19 | import com.google.common.flogger.LogSite; 20 | import java.util.Objects; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | import org.jspecify.annotations.Nullable; 23 | 24 | /** A simplified LogSite implementation used for testing. */ 25 | public final class FakeLogSite extends LogSite { 26 | private static final AtomicInteger uid = new AtomicInteger(); 27 | 28 | /** Creates a fake log site (with plausible behavior) from the given parameters. */ 29 | public static LogSite create( 30 | String className, String methodName, int lineNumber, String sourcePath) { 31 | return new FakeLogSite(className, methodName, lineNumber, sourcePath); 32 | } 33 | 34 | /** Creates a unique fake log site for use as a key when testing shared static maps. */ 35 | public static LogSite unique() { 36 | return create("ClassName", "method_" + uid.incrementAndGet(), 123, "ClassName.java"); 37 | } 38 | 39 | private final String className; 40 | private final String methodName; 41 | private final int lineNumber; 42 | private final String sourcePath; 43 | 44 | private FakeLogSite(String className, String methodName, int lineNumber, String sourcePath) { 45 | this.className = className; 46 | this.methodName = methodName; 47 | this.lineNumber = lineNumber; 48 | this.sourcePath = sourcePath; 49 | } 50 | 51 | @Override 52 | public String getClassName() { 53 | return className; 54 | } 55 | 56 | @Override 57 | public String getMethodName() { 58 | return methodName; 59 | } 60 | 61 | @Override 62 | public int getLineNumber() { 63 | return lineNumber; 64 | } 65 | 66 | @Override 67 | public String getFileName() { 68 | return sourcePath; 69 | } 70 | 71 | @Override 72 | public boolean equals(@Nullable Object obj) { 73 | if (!(obj instanceof FakeLogSite)) { 74 | return false; 75 | } 76 | FakeLogSite other = (FakeLogSite) obj; 77 | return Objects.equals(className, other.className) 78 | && Objects.equals(methodName, other.methodName) 79 | && lineNumber == other.lineNumber 80 | && Objects.equals(sourcePath, other.sourcePath); 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | return Objects.hash(className, methodName, lineNumber, sourcePath); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/util/JavaLangAccessStackGetter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.util; 18 | 19 | import static com.google.common.flogger.util.Checks.checkArgument; 20 | 21 | import sun.misc.JavaLangAccess; 22 | import sun.misc.SharedSecrets; 23 | 24 | /** 25 | * {@link JavaLangAccess} based implementation of {@link StackGetter}. 26 | * 27 | *

Note. This is being compiled separate from the rest of the code, because it uses Java 8 28 | * private api. 29 | */ 30 | final class JavaLangAccessStackGetter implements StackGetter { 31 | private static final JavaLangAccess access = SharedSecrets.getJavaLangAccess(); 32 | 33 | @Override 34 | public StackTraceElement callerOf(Class target, int skipFrames) { 35 | checkArgument(skipFrames >= 0, "skipFrames must be >= 0"); 36 | Throwable throwable = new Throwable(); 37 | int index = findCallerIndex(throwable, target, skipFrames + 1); 38 | return index == -1 ? null : access.getStackTraceElement(throwable, index); 39 | } 40 | 41 | @Override 42 | public StackTraceElement[] getStackForCaller(Class target, int maxDepth, int skipFrames) { 43 | checkArgument(maxDepth == -1 || maxDepth > 0, "maxDepth must be > 0 or -1"); 44 | checkArgument(skipFrames >= 0, "skipFrames must be >= 0"); 45 | Throwable throwable = new Throwable(); 46 | int callerIndex = findCallerIndex(throwable, target, skipFrames + 1); 47 | if (callerIndex == -1) { 48 | return new StackTraceElement[0]; 49 | } 50 | int elementsToAdd = access.getStackTraceDepth(throwable) - callerIndex; 51 | if (maxDepth > 0 && maxDepth < elementsToAdd) { 52 | elementsToAdd = maxDepth; 53 | } 54 | StackTraceElement[] stackTrace = new StackTraceElement[elementsToAdd]; 55 | for (int i = 0; i < elementsToAdd; i++) { 56 | stackTrace[i] = access.getStackTraceElement(throwable, callerIndex + i); 57 | } 58 | return stackTrace; 59 | } 60 | 61 | private int findCallerIndex(Throwable throwable, Class target, int skipFrames) { 62 | int depth = access.getStackTraceDepth(throwable); 63 | boolean foundCaller = false; 64 | String targetClassName = target.getName(); 65 | for (int index = skipFrames; index < depth; index++) { 66 | if (access.getStackTraceElement(throwable, index).getClassName().equals(targetClassName)) { 67 | foundCaller = true; 68 | } else if (foundCaller) { 69 | return index; 70 | } 71 | } 72 | return -1; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/util/RecursionDepth.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.util; 18 | 19 | import java.io.Closeable; 20 | 21 | /** 22 | * A threal local counter, incremented whenever a log statement is being processed by the 23 | * backend. If this value is greater than 1, then reentrant logging has occured, and some code may 24 | * behave differently to try and avoid issues such as unbounded recursion. Logging may even be 25 | * disabled completely if the depth gets too high. 26 | * 27 | *

This class is an internal detail and must not be used outside the core Flogger library. 28 | * Backends which need to know the recursion depth for any reason should call {@code 29 | * Platform.getCurrentRecursionDepth()}. 30 | */ 31 | public final class RecursionDepth implements Closeable { 32 | private static final ThreadLocal holder = new ThreadLocal() { 33 | @Override 34 | protected RecursionDepth initialValue() { 35 | return new RecursionDepth(); 36 | } 37 | }; 38 | 39 | // Android annotates ThreadLocal.get() with @RecentlyNullable, but holder.get() is never null. 40 | @SuppressWarnings("nullness") 41 | private static RecursionDepth getRecursionDepth() { 42 | return holder.get(); 43 | } 44 | 45 | /** Do not call this method directly, use {@code Platform.getCurrentRecursionDepth()}. */ 46 | public static int getCurrentDepth() { 47 | return getRecursionDepth().value; 48 | } 49 | 50 | /** Do not call this method directly, use {@code Platform.getCurrentRecursionDepth()}. */ 51 | public int getValue() { 52 | return value; 53 | } 54 | 55 | /** Internal API for use by core Flogger library. */ 56 | public static RecursionDepth enterLogStatement() { 57 | RecursionDepth depth = getRecursionDepth(); 58 | // Can only reach 0 if it wrapped around completely or someone is manipulating the value badly. 59 | // We really don't expect 2^32 levels of recursion however, so assume it's a bug. 60 | if (++depth.value == 0) { 61 | throw new AssertionError("Overflow of RecursionDepth (possible error in core library)"); 62 | } 63 | return depth; 64 | } 65 | 66 | private int value = 0; 67 | 68 | @Override 69 | public void close() { 70 | if (value > 0) { 71 | value -= 1; 72 | return; 73 | } 74 | // This should never happen if the only callers are inside core library. 75 | throw new AssertionError("Mismatched calls to RecursionDepth (possible error in core library)"); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/StackBasedLogSite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.flogger.util.Checks.checkNotNull; 20 | import static java.lang.Math.max; 21 | 22 | import org.jspecify.annotations.Nullable; 23 | 24 | /** 25 | * A stack based log site which uses information from a given {@code StackTraceElement}. 26 | * 27 | *

Unlike truly unique injected log sites, StackBasedLogSite falls back to using the class name, 28 | * method name and line number for {@code equals()} and {@code hashcode()}. This makes it almost as 29 | * good as a globally unique instance in most cases, except if either of the following is true: 30 | * 31 | *

    32 | *
  • There are two log statements on a single line. 33 | *
  • Line number information is stripped from the class. 34 | *
35 | * 36 | *

This class should not be used directly outside the core Flogger libraries. If you need to 37 | * generate a {@link LogSite} from a {@link StackTraceElement}, use {@link 38 | * com.google.common.flogger.LogSites#logSiteFrom(StackTraceElement) 39 | * LogSites.logSiteFrom(myStackTaceElement)}. 40 | */ 41 | final class StackBasedLogSite extends LogSite { 42 | // StackTraceElement is unmodifiable once created. 43 | private final StackTraceElement stackElement; 44 | 45 | public StackBasedLogSite(StackTraceElement stackElement) { 46 | this.stackElement = checkNotNull(stackElement, "stack element"); 47 | } 48 | 49 | @Override 50 | public String getClassName() { 51 | return stackElement.getClassName(); 52 | } 53 | 54 | @Override 55 | public String getMethodName() { 56 | return stackElement.getMethodName(); 57 | } 58 | 59 | @Override 60 | public int getLineNumber() { 61 | // Prohibit negative numbers (which can appear in stack trace elements) from being returned. 62 | return max(stackElement.getLineNumber(), LogSite.UNKNOWN_LINE); 63 | } 64 | 65 | @Override 66 | public String getFileName() { 67 | return stackElement.getFileName(); 68 | } 69 | 70 | @Override 71 | public boolean equals(@Nullable Object obj) { 72 | return (obj instanceof StackBasedLogSite) 73 | && stackElement.equals(((StackBasedLogSite) obj).stackElement); 74 | } 75 | 76 | @Override 77 | public int hashCode() { 78 | // Note that (unlike other log site implementations) this hash-code appears to include the 79 | // file name when creating a hashcode, but this should be the same every time a stack trace 80 | // element is created, so it shouldn't be a problem. 81 | return stackElement.hashCode(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/testing/FakeLoggerBackend.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.testing; 18 | 19 | import static com.google.common.flogger.util.Checks.checkNotNull; 20 | 21 | import com.google.common.flogger.backend.LogData; 22 | import com.google.common.flogger.backend.LoggerBackend; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.logging.Level; 26 | 27 | /** 28 | * A logger backend which captures all {@code LogData} instances logged to it. This class is 29 | * mutable and not thread safe. 30 | */ 31 | public final class FakeLoggerBackend extends LoggerBackend { 32 | private final String name; 33 | private Level minLevel = Level.INFO; 34 | private final List logged = new ArrayList(); 35 | 36 | /** 37 | * Returns a fake backend with a fixed name. Use this constructor by default unless your tests 38 | * care about the backend's name (which in general, they shouldn't). 39 | */ 40 | public FakeLoggerBackend() { 41 | this("com.example.MyClass"); 42 | } 43 | 44 | /** 45 | * Returns a fake backend with the given name. Use this constructor only if your tests care about 46 | * the backend's name (which in general, they shouldn't). 47 | */ 48 | public FakeLoggerBackend(String name) { 49 | this.name = checkNotNull(name, "name"); 50 | } 51 | 52 | /** Sets the current level of this backend. */ 53 | public void setLevel(Level level) { 54 | this.minLevel = checkNotNull(level, "level"); 55 | } 56 | 57 | /** Returns the number of {@link LogData} entries captured by this backend. */ 58 | public int getLoggedCount() { 59 | return logged.size(); 60 | } 61 | 62 | /** Returns the {@code Nth} {@link LogData} entry captured by this backend. */ 63 | public LogData getLogged(int n) { 64 | return logged.get(n); 65 | } 66 | 67 | /** Asserts about the {@code Nth} logged entry. */ 68 | public LogDataSubject assertLogged(int n) { 69 | return LogDataSubject.assertThat(logged.get(n)); 70 | } 71 | 72 | /** Asserts about the most recent logged entry. */ 73 | public LogDataSubject assertLastLogged() { 74 | return assertLogged(logged.size() - 1); 75 | } 76 | 77 | @Override public String getLoggerName() { 78 | return name; 79 | } 80 | 81 | @Override public boolean isLoggable(Level loggedLevel) { 82 | return loggedLevel.intValue() >= minLevel.intValue(); 83 | } 84 | 85 | @Override public void log(LogData data) { 86 | logged.add(data); 87 | } 88 | 89 | @Override public void handleError(RuntimeException error, LogData badData) { 90 | throw error; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/backend/MetadataKeyValueHandlersTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import com.google.common.collect.ImmutableList; 22 | import com.google.common.collect.ImmutableSet; 23 | import com.google.common.flogger.MetadataKey; 24 | import com.google.common.flogger.MetadataKey.KeyValueHandler; 25 | import java.util.ArrayList; 26 | import java.util.Iterator; 27 | import org.junit.Test; 28 | import org.junit.runner.RunWith; 29 | import org.junit.runners.JUnit4; 30 | 31 | @RunWith(JUnit4.class) 32 | public final class MetadataKeyValueHandlersTest { 33 | private static final MetadataKey single = MetadataKey.single("single", Object.class); 34 | private static final MetadataKey repeated = 35 | MetadataKey.repeated("repeated", Object.class); 36 | private static final MetadataKey ignored = MetadataKey.single("ignored", Object.class); 37 | 38 | private static final class AppendingHandler implements KeyValueHandler { 39 | final ArrayList entries = new ArrayList<>(); 40 | 41 | @Override 42 | public void handle(String label, Object value) { 43 | entries.add(label + "=" + value); 44 | } 45 | } 46 | 47 | private static Iterator iterate(T... values) { 48 | return ImmutableList.copyOf(values).iterator(); 49 | } 50 | 51 | @Test 52 | public void testDefaultValueHandler() { 53 | AppendingHandler handler = new AppendingHandler(); 54 | MetadataKeyValueHandlers.getDefaultValueHandler().handle(single, "value", handler); 55 | assertThat(handler.entries).containsExactly("single=value"); 56 | } 57 | 58 | @Test 59 | public void testDefaultRepeatedValueHandler() { 60 | AppendingHandler handler = new AppendingHandler(); 61 | MetadataKeyValueHandlers.getDefaultRepeatedValueHandler() 62 | .handle(repeated, iterate("foo", "bar"), handler); 63 | assertThat(handler.entries).containsExactly("repeated=foo", "repeated=bar").inOrder(); 64 | } 65 | 66 | @Test 67 | public void testDefaultHandler_ignoresSpecifiedKeys() { 68 | AppendingHandler handler = new AppendingHandler(); 69 | MetadataHandler metadataHandler = 70 | MetadataKeyValueHandlers.getDefaultHandler(ImmutableSet.of(ignored)); 71 | metadataHandler.handle(single, "foo", handler); 72 | metadataHandler.handle(ignored, "ignored", handler); 73 | metadataHandler.handleRepeated(repeated, iterate("bar", "baz"), handler); 74 | assertThat(handler.entries) 75 | .containsExactly("single=foo", "repeated=bar", "repeated=baz") 76 | .inOrder(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/util/Checks.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.util; 18 | 19 | import com.google.errorprone.annotations.CanIgnoreReturnValue; 20 | 21 | /** 22 | * Flogger's own version of the Guava {@code Preconditions} class for simple, often used checks. 23 | */ 24 | public class Checks { 25 | private Checks() {} 26 | 27 | // Warning: The methods in this class may not use String.format() to construct "fancy" error 28 | // messages (because that's not GWT compatible). 29 | 30 | @CanIgnoreReturnValue 31 | public static T checkNotNull(T value, String name) { 32 | if (value == null) { 33 | throw new NullPointerException(name + " must not be null"); 34 | } 35 | return value; 36 | } 37 | 38 | public static void checkArgument(boolean condition, String message) { 39 | if (!condition) { 40 | throw new IllegalArgumentException(message); 41 | } 42 | } 43 | 44 | public static void checkState(boolean condition, String message) { 45 | if (!condition) { 46 | throw new IllegalStateException(message); 47 | } 48 | } 49 | 50 | /** Checks if the given string is a valid metadata identifier. */ 51 | @CanIgnoreReturnValue 52 | public static String checkMetadataIdentifier(String s) { 53 | // Note that we avoid using regular expressions here, since we've not used it anywhere else 54 | // thus far in Flogger (avoid it make it more likely that Flogger can be transpiled). 55 | if (s.isEmpty()) { 56 | throw new IllegalArgumentException("identifier must not be empty"); 57 | } 58 | if (!isLetter(s.charAt(0))) { 59 | throw new IllegalArgumentException("identifier must start with an ASCII letter: " + s); 60 | } 61 | for (int n = 1; n < s.length(); n++) { 62 | char c = s.charAt(n); 63 | if (!isLetter(c) && (c < '0' || c > '9') && c != '_') { 64 | throw new IllegalArgumentException( 65 | "identifier must contain only ASCII letters, digits or underscore: " + s); 66 | } 67 | } 68 | return s; 69 | } 70 | 71 | // WARNING: The reason the we are NOT using method from Character like "isLetter()", 72 | // "isJavaLetter()", "isJavaIdentifierStart()" etc. is that these rely on the Unicode definitions 73 | // of "LETTER", which are not stable between releases. In theory something marked as a letter in 74 | // Unicode could be changed to not be a letter in a later release. There is a notion of stable 75 | // identifiers in Unicode, which is what should be used here, but that needs more investigation. 76 | private static boolean isLetter(char c) { 77 | return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /log4j2/src/test/java/com/google/common/flogger/backend/log4j2/ValueQueueTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.log4j2; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | import static org.junit.Assert.fail; 21 | 22 | import java.util.Arrays; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.junit.runners.JUnit4; 26 | 27 | /** Tests for {@link ValueQueue}. */ 28 | @RunWith(JUnit4.class) 29 | public class ValueQueueTest { 30 | 31 | @Test 32 | public void testValueQueue() { 33 | ValueQueue valueQueue = ValueQueue.appendValueToNewQueue(Arrays.asList(1, 2, 3)); 34 | assertThat(valueQueue.toString()).isEqualTo("[1, 2, 3]"); 35 | } 36 | 37 | @Test 38 | public void assertSingleValue() { 39 | Object valueList = ValueQueue.maybeWrap(1, null); 40 | assertThat(valueList.toString()).isEqualTo("1"); 41 | } 42 | 43 | @Test 44 | public void assertTwoValues_maybeWrap() { 45 | Object valueQueue1 = ValueQueue.maybeWrap(1, null); 46 | ValueQueue valueQueue = (ValueQueue) ValueQueue.maybeWrap(2, valueQueue1); 47 | assertThat(valueQueue.toString()).isEqualTo("[1, 2]"); 48 | } 49 | 50 | @Test 51 | public void assertThreeValues_maybeWrap() { 52 | Object valueQueue1 = ValueQueue.maybeWrap(1, null); 53 | ValueQueue valueQueue2 = (ValueQueue) ValueQueue.maybeWrap(2, valueQueue1); 54 | ValueQueue valueQueue3 = (ValueQueue) ValueQueue.maybeWrap(3, valueQueue2); 55 | assertThat(valueQueue3.toString()).isEqualTo("[1, 2, 3]"); 56 | } 57 | 58 | @Test 59 | public void assertEmptyValueQueue() { 60 | assertThat(ValueQueue.appendValueToNewQueue("").toString()).isEmpty(); 61 | } 62 | 63 | @Test 64 | public void assertValueQueueOfValueQueue() { 65 | ValueQueue valueQueue = ValueQueue.appendValueToNewQueue(Arrays.asList(Arrays.asList(1, 2), 3)); 66 | assertThat(valueQueue.toString()).isEqualTo("[[1, 2], 3]"); 67 | } 68 | 69 | @Test 70 | public void assertListOfValueQueue() { 71 | ValueQueue valueQueue = ValueQueue.appendValueToNewQueue(Arrays.asList(1, 2, 3)); 72 | assertThat(valueQueue.toString()).isEqualTo("[1, 2, 3]"); 73 | } 74 | 75 | @Test 76 | public void assertNestedListOfValueQueue() { 77 | ValueQueue valueQueue = 78 | ValueQueue.appendValueToNewQueue( 79 | Arrays.asList(Arrays.asList(4, 5), Arrays.asList(1, 2, 3))); 80 | assertThat(valueQueue.toString()).isEqualTo("[[4, 5], [1, 2, 3]]"); 81 | } 82 | 83 | @Test 84 | public void assertValueListThrowsNpe() { 85 | try { 86 | ValueQueue.maybeWrap(null, null); 87 | fail(); 88 | } catch (NullPointerException expected) { 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/parser/DefaultBraceStyleMessageParserTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.parser; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | import static org.junit.Assert.fail; 21 | import static org.mockito.ArgumentMatchers.eq; 22 | import static org.mockito.Mockito.mock; 23 | import static org.mockito.Mockito.verify; 24 | 25 | import com.google.common.flogger.backend.FormatChar; 26 | import com.google.common.flogger.backend.FormatOptions; 27 | import com.google.common.flogger.parameter.Parameter; 28 | import com.google.common.flogger.parameter.ParameterVisitor; 29 | import org.junit.Test; 30 | import org.junit.runner.RunWith; 31 | import org.junit.runners.JUnit4; 32 | import org.mockito.ArgumentCaptor; 33 | 34 | @RunWith(JUnit4.class) 35 | public class DefaultBraceStyleMessageParserTest { 36 | private static final BraceStyleMessageParser PARSER = 37 | DefaultBraceStyleMessageParser.getInstance(); 38 | private static final FormatOptions WITH_GROUPING = 39 | FormatOptions.of(FormatOptions.FLAG_SHOW_GROUPING, FormatOptions.UNSET, FormatOptions.UNSET); 40 | 41 | @Test 42 | public void testParseBraceFormat() throws ParseException { 43 | MessageBuilder builder = mock(MessageBuilder.class); 44 | // Parse just the 3 characters representing the brace format specifier between position 6 and 9. 45 | // The -1 indicates that there's no additional formatting information after the index. 46 | PARSER.parseBraceFormatTerm(builder, 1, "Hello {1} World", 6, -1, 9); 47 | 48 | // Capture the parameter created by the parsing of the printf term. 49 | ArgumentCaptor param = ArgumentCaptor.forClass(Parameter.class); 50 | verify(builder) 51 | .addParameter( 52 | eq(6), eq(9), param.capture()); 53 | assertThat(param.getValue().getIndex()).isEqualTo(1); 54 | 55 | // Now visit the parameter and verify the expected callback occurred (doing it this way avoids 56 | // needing to open up methods on the Parameter interface just for testing). 57 | ParameterVisitor out = mock(ParameterVisitor.class); 58 | param.getValue().accept(out, new Object[] {"Answer: ", 42}); 59 | // Check the expected values were passed (decimals should be formatted like "%,d"). 60 | verify(out).visit(eq(42), eq(FormatChar.DECIMAL), eq(WITH_GROUPING)); 61 | } 62 | 63 | @Test 64 | public void testTrailingFormatNotSupportedInBraceFormat() { 65 | try { 66 | PARSER.parseBraceFormatTerm(null, 0, "{0:x}", 0, 3, 5); 67 | fail("expected ParseException"); 68 | } catch (ParseException expected) { 69 | assertThat(expected.getMessage()).contains("[:x]"); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/SamplingRateLimiterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.flogger.LogContext.Key.LOG_SAMPLE_EVERY_N; 20 | import static com.google.common.truth.Truth.assertThat; 21 | 22 | import com.google.common.collect.Range; 23 | import com.google.common.flogger.backend.Metadata; 24 | import com.google.common.flogger.testing.FakeLogSite; 25 | import com.google.common.flogger.testing.FakeMetadata; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.junit.runners.JUnit4; 29 | 30 | @RunWith(JUnit4.class) 31 | public class SamplingRateLimiterTest { 32 | @Test 33 | public void testInvalidCount() { 34 | Metadata metadata = new FakeMetadata().add(LOG_SAMPLE_EVERY_N, 0); 35 | assertThat(SamplingRateLimiter.check(metadata, FakeLogSite.unique())).isNull(); 36 | } 37 | 38 | @Test 39 | public void testPendingCount() { 40 | SamplingRateLimiter limiter = new SamplingRateLimiter(); 41 | // Initially we are not "pending", so disallow logging for an "impossible" sample rate. 42 | assertThat(limiter.pendingCount.get()).isEqualTo(0); 43 | assertThat(limiter.sampleOneIn(Integer.MAX_VALUE)).isEqualTo(RateLimitStatus.DISALLOW); 44 | for (int i = 0; i < 100; i++) { 45 | RateLimitStatus unused = limiter.sampleOneIn(5); 46 | } 47 | // Statistically we should be pending at least once. 48 | int pendingCount = limiter.pendingCount.get(); 49 | assertThat(pendingCount).isGreaterThan(0); 50 | // Now we are pending, we allow logging even for an "impossible" sample rate. 51 | assertThat(limiter.sampleOneIn(Integer.MAX_VALUE)).isNotEqualTo(RateLimitStatus.DISALLOW); 52 | limiter.reset(); 53 | assertThat(limiter.pendingCount.get()).isEqualTo(pendingCount - 1); 54 | } 55 | 56 | @Test 57 | public void testSamplingRate() { 58 | // Chance is less than one-millionth of 1% that this will fail spuriously. 59 | Metadata metadata = new FakeMetadata().add(LOG_SAMPLE_EVERY_N, 2); 60 | assertThat(countNSamples(1000, metadata)).isIn(Range.closed(400, 600)); 61 | 62 | // Expected average is 20 logs out of 1000. Seeing 0 or > 100 is enormously unlikely. 63 | metadata = new FakeMetadata().add(LOG_SAMPLE_EVERY_N, 50); 64 | assertThat(countNSamples(1000, metadata)).isIn(Range.closed(1, 100)); 65 | } 66 | 67 | private static int countNSamples(int n, Metadata metadata) { 68 | LogSite logSite = FakeLogSite.unique(); 69 | int sampled = 0; 70 | while (n-- > 0) { 71 | if (RateLimitStatus.checkStatus( 72 | SamplingRateLimiter.check(metadata, logSite), logSite, metadata) >= 0) { 73 | sampled++; 74 | } 75 | } 76 | return sampled; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/parameter/ParameterVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.parameter; 18 | 19 | import com.google.common.flogger.backend.FormatChar; 20 | import com.google.common.flogger.backend.FormatOptions; 21 | 22 | /** 23 | * A visitor of log message arguments, dispatched by {@code Parameter} instances. 24 | */ 25 | // TODO: When all other refactoring done, rename to ArgumentVisitor 26 | public interface ParameterVisitor { 27 | /** 28 | * Visits a log message argument with formatting specified by {@code %s}, {@code %d} etc... 29 | *

30 | * Note that this method may still visit arguments which represent date/time values if the format 31 | * is not explicit (e.g. {@code log("time=%s", dateTime)}). 32 | * 33 | * @param value the non-null log message argument. 34 | * @param format the printf format specifier. 35 | * @param options formatting options. 36 | */ 37 | void visit(Object value, FormatChar format, FormatOptions options); 38 | 39 | /** 40 | * Visits a date/time log message argument with formatting specified by {@code %t} or similar. 41 | *

42 | * Note that because this method is called based on the specified format (and not the argument 43 | * type) it may visit arguments whose type is not a known date/time value. This is necessary to 44 | * permit new date/time types to be supported by different logging backends (e.g. JodaTime). 45 | * 46 | * @param value the non-null log message argument. 47 | * @param format the date/time format specifier. 48 | * @param options formatting options. 49 | */ 50 | void visitDateTime(Object value, DateTimeFormat format, FormatOptions options); 51 | 52 | /** 53 | * Visits a log message argument for which formatting has already occurred. This method is only 54 | * invoked when non-printf message formatting is used (e.g. brace style formatting). 55 | *

56 | * This method is intended for use by {@code Parameter} implementations which describe formatting 57 | * rules which cannot by represented by either {@link FormatChar} or {@link DateTimeFormat}. This 58 | * method discards formatting and type information, and the visitor implementation may choose to 59 | * reexamine the type of the original argument if doing structural logging. 60 | * 61 | * @param value the original non-null log message argument. 62 | * @param formatted the formatted representation of the argument 63 | */ 64 | void visitPreformatted(Object value, String formatted); 65 | 66 | /** 67 | * Visits a missing argument. This method is called when there is no corresponding value for the 68 | * parameter's argument index. 69 | */ 70 | void visitMissing(); 71 | 72 | /** Visits a null argument. */ 73 | void visitNull(); 74 | } 75 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/context/ScopeType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.context; 18 | 19 | import static com.google.common.flogger.util.Checks.checkNotNull; 20 | 21 | import com.google.common.flogger.LoggingScope; 22 | import com.google.common.flogger.LoggingScopeProvider; 23 | import org.jspecify.annotations.Nullable; 24 | 25 | /** 26 | * Singleton keys which identify different types of scopes which scoped contexts can be bound to. 27 | * 28 | *

To bind a context to a scope type, create the context with that type: 29 | * 30 | *

{@code
31 |  * ScopedLoggingContext.getInstance().newScope(REQUEST).run(() -> someTask(...));
32 |  * }
33 | */ 34 | public final class ScopeType implements LoggingScopeProvider { 35 | /** 36 | * The built in "request" scope. This can be bound to a scoped context in order to provide a 37 | * distinct request scope for each context, allowing stateful logging operations (e.g. rate 38 | * limiting) to be scoped to the current request. 39 | * 40 | *

Enable a request scope using: 41 | * 42 | *

{@code
43 |    * ScopedLoggingContext.getInstance().newScope(REQUEST).run(() -> scopedMethod(x, y, z));
44 |    * }
45 | * 46 | * which runs {@code scopedMethod} with a new "request" scope for the duration of the context. 47 | * 48 | *

Then use per-request rate limiting using: 49 | * 50 | *

{@code
51 |    * logger.atWarning().atMostEvery(5, SECONDS).per(REQUEST).log("Some error message...");
52 |    * }
53 | * 54 | * Note that in order for the request scope to be applied to a log statement, the {@code 55 | * per(REQUEST)} method must still be called; just being inside the request scope isn't enough. 56 | */ 57 | public static final ScopeType REQUEST = create("request"); 58 | 59 | /** 60 | * Creates a new Scope type, which can be used as a singleton key to identify a scope during 61 | * scoped context creation or logging. Callers are expected to retain this key in a static field 62 | * or return it via a static method. Scope types have singleton semantics and two scope types with 63 | * the same name are NOT equivalent. 64 | * 65 | * @param name a debug friendly scope identifier (e.g. "my_batch_job"). 66 | */ 67 | public static ScopeType create(String name) { 68 | return new ScopeType(name); 69 | } 70 | 71 | private final String name; 72 | 73 | private ScopeType(String name) { 74 | this.name = checkNotNull(name, "name"); 75 | } 76 | 77 | 78 | // Called by ScopedLoggingContext to make a new scope instance when a context is installed. 79 | LoggingScope newScope() { 80 | return LoggingScope.create(name); 81 | } 82 | 83 | @Override 84 | public @Nullable LoggingScope getCurrentScope() { 85 | return ContextDataProvider.getInstance().getScope(this); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/system/Clock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.system; 18 | 19 | /** 20 | * A clock to return walltime timestamps for log statements. This is implemented as an abstract 21 | * class (rather than an interface) to reduce to risk of breaking existing implementations if the 22 | * API changes. 23 | * 24 | *

Essential Implementation Restrictions

25 | * 26 | * Any implementation of this API MUST follow the rules listed below to avoid any risk of 27 | * re-entrant code calling during logger initialization. Failure to do so risks creating complex, 28 | * hard to debug, issues with Flogger configuration. 29 | * 30 | *
    31 | *
  1. Implementations MUST NOT attempt any logging in static methods or constructors. 32 | *
  2. Implementations MUST NOT statically depend on any unknown code. 33 | *
  3. Implementations MUST NOT depend on any unknown code in constructors. 34 | *
35 | * 36 | *

Note that logging and calling arbitrary unknown code (which might log) are permitted inside 37 | * the instance methods of this API, since they are not called during platform initialization. The 38 | * easiest way to achieve this is to simply avoid having any non-trivial static fields or any 39 | * instance fields at all in the implementation. 40 | * 41 | *

While this sounds onerous it's not difficult to achieve because this API is a singleton, and 42 | * can delay any actual work until its methods are called. For example if any additional state is 43 | * required in the implementation, it can be held via a "lazy holder" to defer initialization. 44 | * 45 | *

This is a service type

46 | * 47 | *

This type is considered a service type and implemenations may be loaded from the 48 | * classpath via {@link java.util.ServiceLoader} provided the proper service metadata is included in 49 | * the jar file containing the implementation. When creating an implementation of this class, you 50 | * can provide serivce metadata (and thereby allow users to get your implementation just by 51 | * including your jar file) by either manually including a {@code 52 | * META-INF/services/com.google.common.flogger.backend.system.Clock} file containing the name of 53 | * your implementation class or by annotating your implementation class using {@code @AutoService(Clock.class)}. 55 | * See the documentation of both {@link java.util.ServiceLoader} and {@link DefaultPlatform} for 56 | * more information. 57 | */ 58 | public abstract class Clock { 59 | /** 60 | * Returns the current time from the epoch (00:00 1st Jan, 1970) with nanosecond granularity, 61 | * though not necessarily nanosecond precision. This clock measures UTC and is not required to 62 | * handle leap seconds. 63 | */ 64 | public abstract long getCurrentTimeNanos(); 65 | } 66 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/parameter/SimpleParameterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.parameter; 18 | 19 | import static com.google.common.flogger.backend.FormatChar.DECIMAL; 20 | import static com.google.common.flogger.backend.FormatChar.FLOAT; 21 | import static com.google.common.flogger.backend.FormatChar.HEX; 22 | import static com.google.common.flogger.backend.FormatChar.STRING; 23 | import static com.google.common.truth.Truth.assertThat; 24 | 25 | import com.google.common.flogger.backend.FormatChar; 26 | import com.google.common.flogger.backend.FormatOptions; 27 | import com.google.common.flogger.parser.ParseException; 28 | import org.junit.Test; 29 | import org.junit.runner.RunWith; 30 | import org.junit.runners.JUnit4; 31 | 32 | @RunWith(JUnit4.class) 33 | public class SimpleParameterTest { 34 | @Test 35 | public void testCaching() throws ParseException { 36 | // The same instance is used for all types without options up to (at least) 10 indices. 37 | for (FormatChar c : FormatChar.values()) { 38 | for (int n = 0; n < 10; n++) { 39 | assertThat(SimpleParameter.of(n, c, FormatOptions.getDefault())) 40 | .isSameInstanceAs(SimpleParameter.of(n, c, FormatOptions.getDefault())); 41 | } 42 | } 43 | // Different indices do not return the same instances. 44 | assertThat(SimpleParameter.of(1, FormatChar.DECIMAL, FormatOptions.getDefault())) 45 | .isNotSameInstanceAs(SimpleParameter.of(0, FormatChar.DECIMAL, FormatOptions.getDefault())); 46 | // Different format chars do not return the same instances. 47 | assertThat(SimpleParameter.of(0, FormatChar.FLOAT, FormatOptions.getDefault())) 48 | .isNotSameInstanceAs(SimpleParameter.of(0, FormatChar.DECIMAL, FormatOptions.getDefault())); 49 | // Different formatting options do not return the same instances. 50 | assertThat(SimpleParameter.of(0, FormatChar.DECIMAL, FormatOptions.parse("-10", 0, 3, false))) 51 | .isNotSameInstanceAs(SimpleParameter.of(0, FormatChar.DECIMAL, FormatOptions.getDefault())); 52 | } 53 | 54 | @Test 55 | public void testPrintfFormatString() { 56 | assertThat(SimpleParameter.buildFormatString(parseOptions("-20", false), STRING)) 57 | .isEqualTo("%-20s"); 58 | assertThat(SimpleParameter.buildFormatString(parseOptions("0#16", true), HEX)) 59 | .isEqualTo("%#016X"); 60 | // Printing can reorder the flags ... 61 | assertThat(SimpleParameter.buildFormatString(parseOptions("+-20", false), DECIMAL)) 62 | .isEqualTo("%+-20d"); 63 | assertThat(SimpleParameter.buildFormatString(parseOptions(",020.10", false), FLOAT)) 64 | .isEqualTo("%,020.10f"); 65 | } 66 | 67 | private static FormatOptions parseOptions(String s, boolean isUpperCase) { 68 | try { 69 | return FormatOptions.parse(s, 0, s.length(), isUpperCase); 70 | } catch (ParseException parseException) { 71 | throw new RuntimeException(parseException); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/parser/DefaultPrintfMessageParserTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.parser; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | import static org.junit.Assert.fail; 21 | import static org.mockito.ArgumentMatchers.eq; 22 | import static org.mockito.Mockito.mock; 23 | import static org.mockito.Mockito.verify; 24 | 25 | import com.google.common.flogger.backend.FormatChar; 26 | import com.google.common.flogger.backend.FormatOptions; 27 | import com.google.common.flogger.parameter.Parameter; 28 | import com.google.common.flogger.parameter.ParameterVisitor; 29 | import org.junit.Test; 30 | import org.junit.runner.RunWith; 31 | import org.junit.runners.JUnit4; 32 | import org.mockito.ArgumentCaptor; 33 | 34 | @RunWith(JUnit4.class) 35 | public class DefaultPrintfMessageParserTest { 36 | private static final PrintfMessageParser PARSER = DefaultPrintfMessageParser.getInstance(); 37 | 38 | @Test 39 | public void testParsePrintf() throws ParseException { 40 | MessageBuilder builder = mock(MessageBuilder.class); 41 | int unused = PARSER.parsePrintfTerm(builder, 1, "Hello %2$+06.2f World", 6, 9, 14); 42 | 43 | // Capture the parameter created by the parsing of the printf term. 44 | ArgumentCaptor param = ArgumentCaptor.forClass(Parameter.class); 45 | verify(builder) 46 | .addParameter( 47 | eq(6), eq(15), param.capture()); 48 | assertThat(param.getValue().getIndex()).isEqualTo(1); 49 | 50 | // Now visit the parameter and capture its state (doing it this way avoids needing to open up 51 | // methods on the Parameter interface just for testing). 52 | ParameterVisitor out = mock(ParameterVisitor.class); 53 | param.getValue().accept(out, new Object[] {"Answer: ", 42.0}); 54 | 55 | // Recover the captured arguments and check that the right formatting was done. 56 | ArgumentCaptor options = ArgumentCaptor.forClass(FormatOptions.class); 57 | verify(out).visit(eq(42.0), eq(FormatChar.FLOAT), options.capture()); 58 | assertThat(options.getValue().getWidth()).isEqualTo(6); 59 | assertThat(options.getValue().getPrecision()).isEqualTo(2); 60 | assertThat(options.getValue().shouldShowLeadingZeros()).isTrue(); 61 | assertThat(options.getValue().shouldPrefixPlusForPositiveValues()).isTrue(); 62 | } 63 | 64 | @Test 65 | public void testUnknownPrintfFormat() { 66 | try { 67 | int unused = PARSER.parsePrintfTerm(null, 0, "%Q", 0, 1, 1); 68 | fail("expected ParseException"); 69 | } catch (ParseException expected) { 70 | assertThat(expected.getMessage()).contains("[%Q]"); 71 | } 72 | } 73 | 74 | @Test 75 | public void testInvalidPrintfFlags() { 76 | try { 77 | int unused = PARSER.parsePrintfTerm(null, 0, "%0s", 0, 1, 2); 78 | fail("expected ParseException"); 79 | } catch (ParseException expected) { 80 | assertThat(expected.getMessage()).contains("[%0s]"); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/util/StackWalkerStackGetter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.util; 18 | 19 | import static com.google.common.flogger.util.Checks.checkArgument; 20 | 21 | import java.lang.StackWalker.StackFrame; 22 | import java.util.function.Predicate; 23 | import java.util.stream.Stream; 24 | import org.jspecify.annotations.Nullable; 25 | 26 | /** 27 | * StackWalker based implementation of the {@link StackGetter} interface. 28 | * 29 | *

Note, that since this is using Java 9 api, it is being compiled separately from the rest of 30 | * the source code. 31 | */ 32 | // We load this class reflectively, and we fall back to another implementation under older versions. 33 | // However, it's possible that we should avoid even trying it under Android until API Level 36: 34 | // https://r.android.com/3548340 35 | @SuppressWarnings("NewApi") 36 | final class StackWalkerStackGetter implements StackGetter { 37 | private static final StackWalker STACK_WALKER = 38 | StackWalker.getInstance(StackWalker.Option.SHOW_REFLECT_FRAMES); 39 | 40 | public StackWalkerStackGetter() { 41 | // Due to b/241269335, we check in constructor whether this implementation crashes in runtime, 42 | // and CallerFinder should catch any Throwable caused. 43 | StackTraceElement unused = callerOf(StackWalkerStackGetter.class, 0); 44 | } 45 | 46 | @Override 47 | public @Nullable StackTraceElement callerOf(Class target, int skipFrames) { 48 | checkArgument(skipFrames >= 0, "skipFrames must be >= 0"); 49 | return STACK_WALKER.<@Nullable StackTraceElement>walk( 50 | s -> 51 | filterStackTraceAfterTarget(isTargetClass(target), skipFrames, s) 52 | .findFirst() 53 | .orElse(null)); 54 | } 55 | 56 | @Override 57 | public StackTraceElement[] getStackForCaller(Class target, int maxDepth, int skipFrames) { 58 | checkArgument(maxDepth == -1 || maxDepth > 0, "maxDepth must be > 0 or -1"); 59 | checkArgument(skipFrames >= 0, "skipFrames must be >= 0"); 60 | return STACK_WALKER.walk( 61 | s -> 62 | filterStackTraceAfterTarget(isTargetClass(target), skipFrames, s) 63 | .limit(maxDepth == -1 ? Long.MAX_VALUE : maxDepth) 64 | .toArray(StackTraceElement[]::new)); 65 | } 66 | 67 | private Predicate isTargetClass(Class target) { 68 | String name = target.getName(); 69 | return f -> f.getClassName().equals(name); 70 | } 71 | 72 | private Stream filterStackTraceAfterTarget( 73 | Predicate isTargetClass, int skipFrames, Stream s) { 74 | // need to skip + 1 because of the call to the method this method is being called from 75 | return s.skip(skipFrames + 1) 76 | // skip all classes which don't match the name we are looking for 77 | .dropWhile(isTargetClass.negate()) 78 | // then skip all which matches 79 | .dropWhile(isTargetClass) 80 | .map(StackFrame::toStackTraceElement); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/CountingRateLimiter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.flogger.LogContext.Key.LOG_EVERY_N; 20 | import static com.google.common.flogger.RateLimitStatus.DISALLOW; 21 | 22 | import com.google.common.flogger.backend.Metadata; 23 | import java.util.concurrent.atomic.AtomicLong; 24 | import org.jspecify.annotations.Nullable; 25 | 26 | /** 27 | * Rate limiter to support {@code every(N)} functionality. 28 | * 29 | *

Instances of this class are created for each unique {@link LogSiteKey} for which rate limiting 30 | * via the {@code LOG_EVERY_N} metadata key is required. This class implements {@code 31 | * RateLimitStatus} as a mechanism for resetting the rate limiter state. 32 | * 33 | *

Instances of this class are thread safe. 34 | */ 35 | final class CountingRateLimiter extends RateLimitStatus { 36 | private static final LogSiteMap map = 37 | new LogSiteMap() { 38 | @Override 39 | protected CountingRateLimiter initialValue() { 40 | return new CountingRateLimiter(); 41 | } 42 | }; 43 | 44 | /** 45 | * Returns the status of the rate limiter, or {@code null} if the {@code LOG_EVERY_N} metadata was 46 | * not present. 47 | * 48 | *

The rate limiter status is {@code DISALLOW} until the log count exceeds the specified limit, 49 | * and then the limiter switches to its pending state and returns an allow status until it is 50 | * reset. 51 | */ 52 | static @Nullable RateLimitStatus check(Metadata metadata, LogSiteKey logSiteKey) { 53 | Integer rateLimitCount = metadata.findValue(LOG_EVERY_N); 54 | if (rateLimitCount == null) { 55 | // Without rate limiter specific metadata, this limiter has no effect. 56 | return null; 57 | } 58 | return map.get(logSiteKey, metadata).incrementAndCheckLogCount(rateLimitCount); 59 | } 60 | 61 | // By setting the initial value as Integer#MAX_VALUE we ensure that the first time rate limiting 62 | // is checked, the rate limit count (which is only an Integer) must be reached, placing the 63 | // limiter into its pending state immediately. If this is the only limiter used, this corresponds 64 | // to the first log statement always being emitted. 65 | private final AtomicLong invocationCount = new AtomicLong(Integer.MAX_VALUE); 66 | 67 | // Visible for testing. 68 | CountingRateLimiter() {} 69 | 70 | /** 71 | * Increments the invocation count and returns true if it reached the specified rate limit count. 72 | * This is invoked during post-processing if a rate limiting count was set via {@link 73 | * LoggingApi#every(int)}. 74 | */ 75 | // Visible for testing. 76 | RateLimitStatus incrementAndCheckLogCount(int rateLimitCount) { 77 | return invocationCount.incrementAndGet() >= rateLimitCount ? this : DISALLOW; 78 | } 79 | 80 | // Reset function called to move the limiter out of the "pending" state after a log occurs. 81 | @Override 82 | public void reset() { 83 | invocationCount.set(0); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/LogSiteTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import com.google.common.testing.EqualsTester; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.JUnit4; 25 | 26 | @RunWith(JUnit4.class) 27 | public final class LogSiteTest { 28 | 29 | @Test 30 | public void injectedLogSite_internalVsExternalName() { 31 | String className = "com.google.common.flogger.LogSiteTest"; 32 | String internalClassName = "com/google/common/flogger/LogSiteTest"; 33 | 34 | // Using the same class, but represented by the internal vs external name, should 35 | // be considered equal. 36 | new EqualsTester() 37 | .addEqualityGroup(logSite(className), logSite(internalClassName)) 38 | .addEqualityGroup(logSite(className + "2"), logSite(internalClassName + "2")) 39 | .addEqualityGroup(logSite(className + "3"), logSite(internalClassName + "3")) 40 | .testEquals(); 41 | 42 | // The public "className()" method will be the same 43 | assertThat(logSite(className).getClassName()).isEqualTo(className); 44 | assertThat(logSite(internalClassName).getClassName()).isEqualTo(className); 45 | } 46 | 47 | @Test 48 | public void injectedLogSite_distinguishesWhenDifferentOnPackageSeparator() { 49 | String className = "com.google.common.flogger.LogSiteTest"; 50 | String sneakyClassName = "combgoogle/common/flogger/LogSiteTest"; 51 | 52 | new EqualsTester() 53 | .addEqualityGroup(logSite(className)) 54 | .addEqualityGroup(logSite(sneakyClassName)) 55 | .testEquals(); 56 | } 57 | 58 | // This technically passes, but is not what we want, since the botched name isn't a real class 59 | // identifier. The complexity of tracking the separator is not worth it, however. 60 | @Test 61 | public void injectedLogSite_invalidClassName_stillValid() { 62 | String className = "com.google.common.flogger.LogSiteTest"; 63 | String internalClassName = "com/google/common/flogger/LogSiteTest"; 64 | String botchedClassName = "com.google/common.flogger/LogSiteTest"; 65 | 66 | new EqualsTester() 67 | .addEqualityGroup(logSite(className), logSite(internalClassName), logSite(botchedClassName)) 68 | .testEquals(); 69 | } 70 | 71 | @SuppressWarnings("deprecation") // Intentionally calling injectedLogSite to test it. 72 | @Test 73 | public void injectedLogSite_sourceFilePath() { 74 | String className = "com.google.common.flogger.LogSiteTest"; 75 | LogSite logSite = 76 | LogSite.injectedLogSite( 77 | className, "someMethod", 42, "com/google/common/flogger/MyFile.java"); 78 | assertThat(logSite.getFileName()).isEqualTo("MyFile.java"); 79 | assertThat(logSite.getFileName()).isEqualTo("MyFile.java"); 80 | } 81 | 82 | @SuppressWarnings("deprecation") // Intentionally calling injectedLogSite to test it. 83 | private static LogSite logSite(String className) { 84 | return LogSite.injectedLogSite(className, "someMethod", 42, "MyFile.java"); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/SamplingRateLimiter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.flogger.LogContext.Key.LOG_SAMPLE_EVERY_N; 20 | import static com.google.common.flogger.RateLimitStatus.DISALLOW; 21 | 22 | import com.google.common.flogger.backend.Metadata; 23 | import java.util.Random; 24 | import java.util.concurrent.atomic.AtomicInteger; 25 | import org.jspecify.annotations.Nullable; 26 | 27 | /** 28 | * Rate limiter to support {@code onAverageEvery(N)} functionality. 29 | * 30 | *

Instances of this class are created for each unique {@link LogSiteKey} for which rate limiting 31 | * via the {@code LOG_SAMPLE_EVERY_N} metadata key is required. This class implements {@code 32 | * RateLimitStatus} as a mechanism for resetting its own state. 33 | * 34 | *

This class is thread safe. 35 | */ 36 | final class SamplingRateLimiter extends RateLimitStatus { 37 | private static final LogSiteMap map = 38 | new LogSiteMap() { 39 | @Override 40 | protected SamplingRateLimiter initialValue() { 41 | return new SamplingRateLimiter(); 42 | } 43 | }; 44 | 45 | static @Nullable RateLimitStatus check(Metadata metadata, LogSiteKey logSiteKey) { 46 | Integer rateLimitCount = metadata.findValue(LOG_SAMPLE_EVERY_N); 47 | if (rateLimitCount == null || rateLimitCount <= 0) { 48 | // Without valid rate limiter specific metadata, this limiter has no effect. 49 | return null; 50 | } 51 | return map.get(logSiteKey, metadata).sampleOneIn(rateLimitCount); 52 | } 53 | 54 | // Even though Random is synchonized, we have to put it in a ThreadLocal to avoid thread 55 | // contention. We cannot use ThreadLocalRandom (yet) due to JDK level. 56 | private static final ThreadLocal random = new ThreadLocal() { 57 | @Override 58 | protected Random initialValue() { 59 | return new Random(); 60 | } 61 | }; 62 | 63 | // Android annotates ThreadLocal.get() with @RecentlyNullable, but random.get() is never null. 64 | @SuppressWarnings("nullness") 65 | private static Random getRandom() { 66 | return random.get(); 67 | } 68 | 69 | // Visible for testing. 70 | final AtomicInteger pendingCount = new AtomicInteger(); 71 | 72 | // Visible for testing. 73 | SamplingRateLimiter() {} 74 | 75 | RateLimitStatus sampleOneIn(int rateLimitCount) { 76 | // Always "roll the dice" and adjust the count if necessary (even if we were already 77 | // pending). This means that in the long run we will account for every time we roll a 78 | // zero and the number of logs will end up statistically close to 1-in-N (even if at 79 | // times they can be "bursty" due to the action of other rate limiting mechanisms). 80 | int pending; 81 | if (getRandom().nextInt(rateLimitCount) == 0) { 82 | pending = pendingCount.incrementAndGet(); 83 | } else { 84 | pending = pendingCount.get(); 85 | } 86 | return pending > 0 ? this : DISALLOW; 87 | } 88 | 89 | @Override 90 | public void reset() { 91 | pendingCount.decrementAndGet(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/util/CallerFinderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.util; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import org.jspecify.annotations.Nullable; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.JUnit4; 25 | 26 | @RunWith(JUnit4.class) 27 | public class CallerFinderTest { 28 | /** 29 | * A sanity check in case we ever discover a platform where the class name in the stack trace does 30 | * not match Class.getName() - this is never quite guaranteed by the JavaDoc in the JDK but is 31 | * relied upon during log site analysis. 32 | */ 33 | @Test 34 | public void testStackTraceUsesClassGetName() { 35 | // Simple case for a top-level named class. 36 | assertThat(new Throwable().getStackTrace()[0].getClassName()) 37 | .isEqualTo(CallerFinderTest.class.getName()); 38 | 39 | // Anonymous inner class. 40 | Object obj = 41 | new Object() { 42 | @Override 43 | public String toString() { 44 | return new Throwable().getStackTrace()[0].getClassName(); 45 | } 46 | }; 47 | assertThat(obj.toString()).isEqualTo(obj.getClass().getName()); 48 | } 49 | 50 | /** Fake class that emulates some code calling a log method. */ 51 | private static class UserCode { 52 | final LoggerCode logger; 53 | 54 | UserCode(LoggerCode library) { 55 | this.logger = library; 56 | } 57 | 58 | void invokeUserCode() { 59 | loggingMethod(); 60 | } 61 | 62 | void loggingMethod() { 63 | logger.logMethod(); 64 | } 65 | } 66 | 67 | /** Fake class that emulates the logging library which eventually calls 'findCallerOf()'. */ 68 | private static class LoggerCode { 69 | final int skipCount; 70 | @Nullable StackTraceElement caller = null; 71 | 72 | public LoggerCode(int skipCount) { 73 | this.skipCount = skipCount; 74 | } 75 | 76 | void logMethod() { 77 | internalMethodOne(); 78 | } 79 | 80 | void internalMethodOne() { 81 | internalMethodTwo(); 82 | } 83 | 84 | void internalMethodTwo() { 85 | caller = CallerFinder.findCallerOf(LoggerCode.class, skipCount); 86 | } 87 | } 88 | 89 | @Test 90 | public void testFindCallerOf() { 91 | // There are 2 internal methods (not including the log method itself) in our fake library. 92 | LoggerCode library = new LoggerCode(2); 93 | UserCode code = new UserCode(library); 94 | code.invokeUserCode(); 95 | assertThat(library.caller.getClassName()).isEqualTo(UserCode.class.getName()); 96 | assertThat(library.caller.getMethodName()).isEqualTo("loggingMethod"); 97 | } 98 | 99 | @Test 100 | public void testFindCallerOfBadOffset() { 101 | // If the minimum offset exceeds the number of internal methods, the find fails. 102 | LoggerCode library = new LoggerCode(3); 103 | UserCode code = new UserCode(library); 104 | code.invokeUserCode(); 105 | assertThat(library.caller).isNull(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/LogPerBucketingStrategyTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import java.util.Arrays; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.JUnit4; 25 | 26 | @RunWith(JUnit4.class) 27 | public final class LogPerBucketingStrategyTest { 28 | @Test 29 | public void knownBounded() { 30 | Object anyKey = new Object(); 31 | assertThat(LogPerBucketingStrategy.knownBounded().apply(anyKey)).isSameInstanceAs(anyKey); 32 | assertThat(LogPerBucketingStrategy.knownBounded().toString()) 33 | .isEqualTo("LogPerBucketingStrategy[KnownBounded]"); 34 | } 35 | 36 | @Test 37 | public void byClass() { 38 | Object anyKey = new Object(); 39 | assertThat(LogPerBucketingStrategy.byClass().apply(anyKey)).isSameInstanceAs(anyKey.getClass()); 40 | assertThat(LogPerBucketingStrategy.byClass().toString()) 41 | .isEqualTo("LogPerBucketingStrategy[ByClass]"); 42 | } 43 | 44 | static final class NotASystemClass {} 45 | 46 | @Test 47 | public void byClassName() { 48 | Object anyKey = new NotASystemClass(); 49 | assertThat(LogPerBucketingStrategy.byClassName().apply(anyKey)) 50 | .isSameInstanceAs("com.google.common.flogger.LogPerBucketingStrategyTest$NotASystemClass"); 51 | assertThat(LogPerBucketingStrategy.byClassName().toString()) 52 | .isEqualTo("LogPerBucketingStrategy[ByClassName]"); 53 | } 54 | 55 | @Test 56 | public void forKnownKeys() { 57 | LogPerBucketingStrategy strategy = 58 | LogPerBucketingStrategy.forKnownKeys(Arrays.asList("foo", 23)); 59 | assertThat(strategy.apply("foo")).isEqualTo(0); 60 | assertThat(strategy.apply("bar")).isNull(); 61 | // Default boxing rules apply. 62 | assertThat(strategy.apply(23)).isEqualTo(1); 63 | assertThat(strategy.apply(23.0)).isNull(); 64 | assertThat(strategy.toString()).isEqualTo("LogPerBucketingStrategy[ForKnownKeys(foo, 23)]"); 65 | } 66 | 67 | @Test 68 | public void byHashcode() { 69 | Object key = 70 | new Object() { 71 | @Override 72 | public int hashCode() { 73 | return -1; 74 | } 75 | }; 76 | // Show that the strategy choice changes the bucketed value as expected. To maximize the Integer 77 | // caching done by the JVM, the expected value has 128 subtracted from it. 78 | assertThat(LogPerBucketingStrategy.byHashCode(1).apply(key)).isSameInstanceAs(-128); 79 | assertThat(LogPerBucketingStrategy.byHashCode(30).apply(key)).isSameInstanceAs(29 - 128); 80 | assertThat(LogPerBucketingStrategy.byHashCode(10).apply(key)).isSameInstanceAs(9 - 128); 81 | // Max cached value is 127 (corresponding to a modulo of 255). 82 | assertThat(LogPerBucketingStrategy.byHashCode(256).apply(key)).isSameInstanceAs(127); 83 | // Above this we cannot assume singleton semantics. 84 | assertThat(LogPerBucketingStrategy.byHashCode(257).apply(key)).isEqualTo(128); 85 | 86 | assertThat(LogPerBucketingStrategy.byHashCode(10).toString()) 87 | .isEqualTo("LogPerBucketingStrategy[ByHashCode(10)]"); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/FluentLoggerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import com.google.common.flogger.FluentLogger.Context; 22 | import com.google.common.flogger.testing.FakeLoggerBackend; 23 | import java.util.logging.Level; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.junit.runners.JUnit4; 27 | 28 | /** 29 | * Fluent loggers are typically very simple classes whose only real responsibility is as a factory 30 | * for a specific API implementation. As such it needs very few tests itself. 31 | * 32 | * See LogContextTest.java for the vast majority of tests related to base logging behaviour. 33 | */ 34 | @RunWith(JUnit4.class) 35 | public class FluentLoggerTest { 36 | @Test 37 | public void testCreate() { 38 | FluentLogger logger = FluentLogger.forEnclosingClass(); 39 | assertThat(logger.getName()).isEqualTo(FluentLoggerTest.class.getName()); 40 | 41 | // Note that this one-to-one binding of loggers and backends is not strictly necessary and in 42 | // future it's plausible that a configured backend factory might return backends shared with 43 | // many loggers. In that situation, the logger name must still be the enclosing class name 44 | // (held separately by the logger itself) while the backend name could differ. 45 | assertThat(logger.getBackend().getLoggerName()).isEqualTo(FluentLoggerTest.class.getName()); 46 | } 47 | 48 | @Test 49 | public void testNoOp() { 50 | FakeLoggerBackend backend = new FakeLoggerBackend(); 51 | FluentLogger logger = new FluentLogger(backend); 52 | backend.setLevel(Level.INFO); 53 | 54 | // Down to and including the configured log level are not the no-op instance. 55 | assertThat(logger.atSevere()).isNotSameInstanceAs(FluentLogger.NO_OP); 56 | assertThat(logger.atSevere()).isInstanceOf(Context.class); 57 | assertThat(logger.atWarning()).isNotSameInstanceAs(FluentLogger.NO_OP); 58 | assertThat(logger.atWarning()).isInstanceOf(Context.class); 59 | assertThat(logger.atInfo()).isNotSameInstanceAs(FluentLogger.NO_OP); 60 | assertThat(logger.atInfo()).isInstanceOf(Context.class); 61 | 62 | // Below the configured log level you only get the singleton no-op instance. 63 | assertThat(logger.atFine()).isSameInstanceAs(FluentLogger.NO_OP); 64 | assertThat(logger.atFiner()).isSameInstanceAs(FluentLogger.NO_OP); 65 | assertThat(logger.atFinest()).isSameInstanceAs(FluentLogger.NO_OP); 66 | 67 | // Just verify that logs below the current log level are discarded. 68 | logger.atFine().log("DISCARDED"); 69 | logger.atFiner().log("DISCARDED"); 70 | logger.atFinest().log("DISCARDED"); 71 | assertThat(backend.getLoggedCount()).isEqualTo(0); 72 | 73 | // But those at or above are passed to the backend. 74 | logger.atInfo().log("LOGGED"); 75 | assertThat(backend.getLoggedCount()).isEqualTo(1); 76 | 77 | backend.setLevel(Level.OFF); 78 | assertThat(logger.atSevere()).isSameInstanceAs(FluentLogger.NO_OP); 79 | 80 | backend.setLevel(Level.ALL); 81 | assertThat(logger.atFinest()).isNotSameInstanceAs(FluentLogger.NO_OP); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/testing/TestLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.testing; 18 | 19 | import com.google.common.flogger.AbstractLogger; 20 | import com.google.common.flogger.LogContext; 21 | import com.google.common.flogger.LoggingApi; 22 | import com.google.common.flogger.backend.LoggerBackend; 23 | import com.google.common.flogger.parser.DefaultPrintfMessageParser; 24 | import com.google.common.flogger.parser.MessageParser; 25 | import java.util.logging.Level; 26 | 27 | /** 28 | * Helper class for unit tests which need to test backend or context behavior. Unlike normal 29 | * logger instances, this one can be reconfigured dynamically and has specific methods for 30 | * injecting timestamps and forcing log statements. 31 | * 32 | *

This class is mutable and not thread safe. 33 | */ 34 | public final class TestLogger extends AbstractLogger { 35 | // Midnight Jan 1st, 2000 (GMT) 36 | private static final long DEFAULT_TIMESTAMP_NANOS = 946684800000000000L; 37 | 38 | public interface Api extends LoggingApi { } 39 | 40 | /** Returns a test logger for the default logging API. */ 41 | public static TestLogger create(LoggerBackend backend) { 42 | return new TestLogger(backend); 43 | } 44 | 45 | /** Constructs a test logger with the given backend. */ 46 | private TestLogger(LoggerBackend backend) { 47 | super(backend); 48 | } 49 | 50 | @Override 51 | public Api at(Level level) { 52 | return at(level, DEFAULT_TIMESTAMP_NANOS); 53 | } 54 | 55 | /** Logs at the given level, with the specified nanosecond timestamp. */ 56 | @SuppressWarnings("GoodTime") // should accept a java.time.Instant 57 | public Api at(Level level, long timestampNanos) { 58 | return new TestContext(level, false, timestampNanos); 59 | } 60 | 61 | /** Forces logging at the given level. */ 62 | public Api forceAt(Level level) { 63 | return forceAt(level, DEFAULT_TIMESTAMP_NANOS); 64 | } 65 | 66 | /** Forces logging at the given level, with the specified nanosecond timestamp. */ 67 | @SuppressWarnings("GoodTime") // should accept a java.time.Instant 68 | public Api forceAt(Level level, long timestampNanos) { 69 | return new TestContext(level, true, timestampNanos); 70 | } 71 | 72 | /** Logging context implementing the basic logging API. */ 73 | private final class TestContext extends LogContext implements Api { 74 | private TestContext(Level level, boolean isForced, long timestampNanos) { 75 | super(level, isForced, timestampNanos); 76 | } 77 | 78 | @Override 79 | protected TestLogger getLogger() { 80 | return TestLogger.this; 81 | } 82 | 83 | @Override 84 | protected Api api() { 85 | return this; 86 | } 87 | 88 | @Override 89 | protected Api noOp() { 90 | throw new UnsupportedOperationException( 91 | "There is no no-op implementation of the logging API for the testing logger."); 92 | } 93 | 94 | @Override 95 | protected final MessageParser getMessageParser() { 96 | return DefaultPrintfMessageParser.getInstance(); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /api/src/main/java/com/google/common/flogger/backend/system/SimpleLogRecord.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend.system; 18 | 19 | import com.google.common.flogger.LogContext; 20 | import com.google.common.flogger.backend.LogData; 21 | import com.google.common.flogger.backend.Metadata; 22 | import java.util.logging.LogRecord; 23 | 24 | /** 25 | * An eagerly evaluating {@link LogRecord} which is created by the Fluent Logger frontend and can be 26 | * passed to a normal log {@link java.util.logging.Handler} instance for output. 27 | */ 28 | public final class SimpleLogRecord extends AbstractLogRecord { 29 | /** Creates a {@link SimpleLogRecord} for a normal log statement from the given data. */ 30 | public static SimpleLogRecord create(LogData data, Metadata scope) { 31 | return new SimpleLogRecord(data, scope); 32 | } 33 | 34 | /** @deprecated Use create(LogData data, Metadata scope) and pass scoped metadata in. */ 35 | @Deprecated 36 | public static SimpleLogRecord create(LogData data) { 37 | return create(data, Metadata.empty()); 38 | } 39 | 40 | /** Creates a {@link SimpleLogRecord} in the case of an error during logging. */ 41 | public static SimpleLogRecord error(RuntimeException error, LogData data, Metadata scope) { 42 | return new SimpleLogRecord(error, data, scope); 43 | } 44 | 45 | /** @deprecated Use error(LogData data, Metadata scope) and pass scoped metadata in. */ 46 | @Deprecated 47 | public static SimpleLogRecord error(RuntimeException error, LogData data) { 48 | return error(error, data, Metadata.empty()); 49 | } 50 | 51 | private SimpleLogRecord(LogData data, Metadata scope) { 52 | super(data, scope); 53 | setThrown(getMetadataProcessor().getSingleValue(LogContext.Key.LOG_CAUSE)); 54 | 55 | // Calling getMessage() formats and caches the formatted message in the AbstractLogRecord. 56 | // 57 | // IMPORTANT: Conceptually there's no need to format the log message here, since backends can 58 | // choose to format messages in different ways or log structurally, so it's not obviously a 59 | // win to format things here first. Formatting would otherwise be done by AbstractLogRecord 60 | // when getMessage() is called, and the results cached; so the only effect of being "lazy" 61 | // should be that formatting (and thus calls to the toString() methods of arguments) happens 62 | // later in the same log statement. 63 | // 64 | // However ... due to bad use of locking in core JDK log handler classes, any lazy formatting 65 | // of log arguments (i.e. in the Handler's "publish()" method) can be done with locks held, 66 | // and thus risks deadlock. We can mitigate the risk by formatting the message string early 67 | // (i.e. here). This is wasteful in cases where this message is never needed (e.g. structured 68 | // logging) but necessary when using many of the common JDK handlers (e.g. StreamHandler, 69 | // FileHandler etc.) and it's impossible to know which handlers are being used. 70 | String unused = getMessage(); 71 | } 72 | 73 | private SimpleLogRecord(RuntimeException error, LogData data, Metadata scope) { 74 | // In the case of an error, the base class handles everything as there's no specific formatting. 75 | super(error, data, scope); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /api/src/test/java/com/google/common/flogger/backend/LogSiteFormattersTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Flogger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.common.flogger.backend; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import com.google.common.flogger.LogSite; 22 | import com.google.common.flogger.testing.FakeLogSite; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.junit.runners.JUnit4; 26 | 27 | @RunWith(JUnit4.class) 28 | public final class LogSiteFormattersTest { 29 | 30 | @Test 31 | public void testInvalidLogSite() { 32 | for (LogSiteFormatter formatter : LogSiteFormatters.values()) { 33 | StringBuilder out = new StringBuilder(); 34 | assertThat(formatter.appendLogSite(LogSite.INVALID, out)).isFalse(); 35 | assertThat(out.toString()).isEmpty(); 36 | } 37 | } 38 | 39 | @Test 40 | public void testAppendLogSite_default() { 41 | StringBuilder out = new StringBuilder(); 42 | LogSite logSite = FakeLogSite.create("", "", 32, "Ignored.java"); 43 | assertThat(LogSiteFormatters.DEFAULT.appendLogSite(logSite, out)).isTrue(); 44 | assertThat(out.toString()).isEqualTo(".:32"); 45 | } 46 | 47 | @Test 48 | public void testAppendLogSite_noOp() { 49 | StringBuilder out = new StringBuilder(); 50 | LogSite logSite = FakeLogSite.create("", "", 32, "Ignored.java"); 51 | assertThat(LogSiteFormatters.NO_OP.appendLogSite(logSite, out)).isFalse(); 52 | assertThat(out.toString()).isEmpty(); 53 | } 54 | 55 | @Test 56 | public void testAppendLogSite_simpleClassname_qualifiedName_unqualified() { 57 | StringBuilder out = new StringBuilder(); 58 | LogSite logSite = 59 | FakeLogSite.create( 60 | "com.google.common.flogger.backend.LogSiteFormattersTest", 61 | "testMethod", 62 | 42, 63 | "Ignored.java"); 64 | assertThat(LogSiteFormatters.SIMPLE_CLASSNAME.appendLogSite(logSite, out)).isTrue(); 65 | assertThat(out.toString()).isEqualTo("LogSiteFormattersTest.testMethod:42"); 66 | } 67 | 68 | @Test 69 | public void testAppendLogSite_simpleClassname_unqualifiedName_all() { 70 | StringBuilder out = new StringBuilder(); 71 | LogSite logSite = FakeLogSite.create("LogSiteFormattersTest", "testMethod", 55, "Ignored.java"); 72 | assertThat(LogSiteFormatters.SIMPLE_CLASSNAME.appendLogSite(logSite, out)).isTrue(); 73 | assertThat(out.toString()).isEqualTo("LogSiteFormattersTest.testMethod:55"); 74 | } 75 | 76 | @Test 77 | public void testAppendLogSite_simpleClassname_trailingDot_all() { 78 | StringBuilder out = new StringBuilder(); 79 | LogSite logSite = 80 | FakeLogSite.create("LogSiteFormattersTest.", "testMethod", 63, "Ignored.java"); 81 | assertThat(LogSiteFormatters.SIMPLE_CLASSNAME.appendLogSite(logSite, out)).isTrue(); 82 | assertThat(out.toString()).isEqualTo("LogSiteFormattersTest..testMethod:63"); 83 | } 84 | 85 | @Test 86 | public void testAppendLogSite_simpleClassname_fakeClassName() { 87 | StringBuilder out = new StringBuilder(); 88 | LogSite logSite = FakeLogSite.create("", "", 32, "Ignored.java"); 89 | assertThat(LogSiteFormatters.SIMPLE_CLASSNAME.appendLogSite(logSite, out)).isTrue(); 90 | assertThat(out.toString()).isEqualTo(".:32"); 91 | } 92 | } 93 | --------------------------------------------------------------------------------