├── .editorconfig
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── ------help.md
│ ├── ---bug-report.md
│ └── ---feature-request.md
├── pull_request_template.md
└── workflows
│ ├── build.yml
│ ├── daily.yml
│ ├── java_build.yml
│ ├── on_merge.yml
│ └── on_push.yml
├── .gitignore
├── .mvn
├── extensions.xml
├── settings.xml
└── wrapper
│ └── maven-wrapper.properties
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs
├── context
│ └── README.md
├── events
│ └── README.md
├── info
│ ├── concept
│ │ └── README.md
│ └── errorhandling
│ │ └── README.md
├── integrations
│ └── README.md
├── registers
│ └── README.md
├── schedulers
│ └── README.md
└── services
│ ├── README.md
│ ├── httpclient
│ └── README.md
│ ├── httpserver
│ └── README.md
│ ├── logger
│ └── README.md
│ └── metricservice
│ └── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
└── java
│ └── org
│ └── nanonative
│ └── nano
│ ├── core
│ ├── Nano.java
│ ├── NanoBase.java
│ ├── NanoServices.java
│ ├── NanoThreads.java
│ └── model
│ │ ├── Context.java
│ │ ├── NanoThread.java
│ │ ├── Scheduler.java
│ │ ├── Service.java
│ │ └── Unhandled.java
│ ├── helper
│ ├── ExRunnable.java
│ ├── LockedBoolean.java
│ ├── NanoUtils.java
│ ├── config
│ │ └── ConfigRegister.java
│ └── event
│ │ ├── EventChannelRegister.java
│ │ └── model
│ │ └── Event.java
│ └── services
│ ├── http
│ ├── HttpClient.java
│ ├── HttpServer.java
│ └── model
│ │ ├── ContentType.java
│ │ ├── HttpHeaders.java
│ │ ├── HttpMethod.java
│ │ └── HttpObject.java
│ ├── logging
│ ├── LogFormatRegister.java
│ ├── LogFormatterConsole.java
│ ├── LogFormatterJson.java
│ ├── LogService.java
│ └── model
│ │ └── LogLevel.java
│ └── metric
│ ├── logic
│ └── MetricService.java
│ └── model
│ ├── MetricCache.java
│ ├── MetricType.java
│ └── MetricUpdate.java
└── test
├── java
└── org
│ └── nanonative
│ └── nano
│ ├── core
│ ├── NanoTest.java
│ ├── config
│ │ └── TestConfig.java
│ └── model
│ │ ├── ConfigTest.java
│ │ ├── ContextTest.java
│ │ ├── LockedBooleanTest.java
│ │ ├── NanoThreadTest.java
│ │ ├── SchedulerTest.java
│ │ ├── ServiceTest.java
│ │ └── UnhandledTest.java
│ ├── examples
│ ├── ErrorHandling.java
│ ├── HttpReceive.java
│ ├── HttpSend.java
│ ├── Kazim.java
│ ├── MetricCreation.java
│ └── Yuna.java
│ ├── model
│ ├── EventChannelRegisterTest.java
│ ├── HttpObjectTest.java
│ └── TestService.java
│ └── services
│ ├── http
│ └── logic
│ │ └── HttpClientTest.java
│ └── metric
│ ├── logic
│ └── MetricServiceTest.java
│ └── model
│ └── MetricCacheTest.java
└── resources
├── Nano.png
├── application-local.properties
├── application.properties
├── junit-platform.properties
└── tiny_java.png
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [ NanoNative ]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: ["https://www.paypal.com/donate/?hosted_button_id=HFHFUT3G6TZF6"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/------help.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F468\U0001F4BB Help"
3 | about: Help needed
4 | title: ''
5 | labels: help wanted
6 | assignees: ''
7 |
8 | ---
9 |
10 | **What do you want to achieve?**
11 | A clear and concise description of what you want to achieve.
12 |
13 | **What did you try already?**
14 | A clear and concise description of what did you already try out.
15 |
16 | **Operating System**
17 | > N/A
18 |
19 | **Project Version**
20 | > N/A
21 |
22 | **Additional context**
23 | Add any other context about the problem here.
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Bug report"
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **What happened?**
11 | A clear and concise description of what the bug is.
12 |
13 | **How can we reproduce the issue?**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Relevant log output**
21 | > N/A
22 |
23 | **Operating System**
24 | > N/A
25 |
26 | **Project Version**
27 | > N/A
28 |
29 | **Expected behavior**
30 | A clear and concise description of what you expected to happen.
31 |
32 | **Screenshots**
33 | If applicable, add screenshots to help explain your problem.
34 |
35 | **Additional context**
36 | Add any other context about the problem here.
37 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F680 Feature request"
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Operating System**
20 | > N/A
21 |
22 | **Project Version**
23 | > N/A
24 |
25 | **Additional context**
26 | Add any other context or screenshots about the feature request here.
27 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | Closes #000
2 |
3 | ### Types of changes
4 |
5 | - [ ] Bug fix (non-breaking change which fixes an issue)
6 | - [ ] New feature (non-breaking change which adds functionality)
7 | - [ ] Breaking change (fix or feature that would cause existing functionality to change)
8 |
9 | ## Motivation
10 | > Why is this change required? What problem does it solve?
11 |
12 | ## Changes
13 | > Behaviour, Functionality, Screenshots, etc.
14 |
15 | ## Success Check
16 | > How can we see or measure the change?
17 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: "Build"
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | run_update:
7 | type: boolean
8 | default: false
9 | required: false
10 | description: "Tries to update the repository like maven/gradle wrapper and maven properties"
11 | run_test:
12 | type: boolean
13 | default: true
14 | required: false
15 | description: "Runs maven/gradle tests"
16 | run_deploy:
17 | type: choice
18 | required: false
19 | default: "disabled"
20 | description: "version increment (Main branch with changes only) [major, minor, patch, rc, disabled]"
21 | options:
22 | - "disabled"
23 | - "major"
24 | - "minor"
25 | - "patch"
26 | - "rc"
27 | ref:
28 | type: string
29 | required: false
30 | description: "[ref] e.g. branch, tag or commit to checkout [default: github_ref_name || github_head_ref ]"
31 |
32 | jobs:
33 | builld:
34 | uses: ./.github/workflows/java_build.yml
35 | with:
36 | ref: ${{ github.event.inputs.ref || github.ref || github.ref_name || github.head_ref }}
37 | run_update: ${{ inputs.run_update }}
38 | run_test: ${{ inputs.run_test }}
39 | run_deploy: ${{ inputs.run_deploy }}
40 | secrets: inherit
41 |
--------------------------------------------------------------------------------
/.github/workflows/daily.yml:
--------------------------------------------------------------------------------
1 | name: "Daily"
2 |
3 | on:
4 | schedule:
5 | - cron: '0 7 * * *'
6 | workflow_dispatch:
7 |
8 | jobs:
9 | builld:
10 | uses: ./.github/workflows/java_build.yml
11 | with:
12 | run_update: true
13 | run_test: true
14 | run_deploy: patch
15 | secrets: inherit
16 |
--------------------------------------------------------------------------------
/.github/workflows/on_merge.yml:
--------------------------------------------------------------------------------
1 | name: "On Merge"
2 |
3 | on:
4 | pull_request_target:
5 | types: [ closed ]
6 | branches:
7 | - main
8 | - master
9 | - default
10 | paths-ignore:
11 | - '*.md'
12 | - '*.cmd'
13 | - '*.bat'
14 | - '*.sh'
15 | - 'FOUNDING.yml'
16 | - 'FOUNDING.yaml'
17 | - '.editorconfig'
18 | - '.gitignore'
19 | - 'docs/**'
20 | - '.github/**'
21 | - '.mvn/**'
22 | - '.gradle/**'
23 | jobs:
24 | builld:
25 | if: github.event.pull_request.merged == true
26 | uses: ./.github/workflows/java_build.yml
27 | with:
28 | ref: ${{ github.ref_name }}
29 | run_update: false # Updates only on main branches
30 | run_test: true # Always run tests
31 | run_deploy: disabled # Never deploy on non-main branches
32 | secrets: inherit
33 |
--------------------------------------------------------------------------------
/.github/workflows/on_push.yml:
--------------------------------------------------------------------------------
1 | name: "On Push"
2 |
3 | on:
4 | push:
5 | branches-ignore:
6 | - main
7 | - master
8 | - default
9 |
10 | jobs:
11 | builld:
12 | uses: ./.github/workflows/java_build.yml
13 | with:
14 | ref: ${{ github.ref_name }}
15 | run_update: false # Updates only on main branches
16 | run_test: true # Always run tests
17 | run_deploy: disabled # Never deploy on non-main branches
18 | secrets: inherit
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### STS ###
2 | .apt_generated
3 | .classpath
4 | .factorypath
5 | .project
6 | .settings
7 | .springBeans
8 | .sts4-cache
9 | /bin/
10 | !**/src/main/**/bin/
11 | !**/src/test/**/bin/
12 | hs_err_pid*.log
13 |
14 | ### NetBeans ###
15 | /nbproject/private/
16 | /nbbuild/
17 | /nbdist/
18 | /.nb-gradle/
19 | /temp
20 |
21 | ### Editors ###
22 | .vscode/
23 | coverage
24 | target/
25 | .idea
26 | *.iws
27 | *.iml
28 | *.ipr
29 | out/
30 | !**/src/main/**/out/
31 | !**/src/test/**/out/
32 | .gradle
33 | build/
34 | !gradle/wrapper/gradle-wrapper.jar
35 | !**/src/main/**/build/
36 | !**/src/test/**/build/
37 | !**/cdk.out
38 | infrastructure/cdk.out
39 | functions/cdk.out
40 | common/cdk.out
41 | *.DS_Store
42 | .venv
43 | __pycache__
44 | *.bat
45 |
46 | /cc-reporter*
47 | /public-key.asc
48 |
49 | lib/
50 | node_modules
51 |
--------------------------------------------------------------------------------
/.mvn/extensions.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | com.github.gzm55.maven
5 | project-settings-extension
6 | 0.1.1
7 |
8 |
--------------------------------------------------------------------------------
/.mvn/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | gpg
6 |
7 | true
8 |
9 |
10 | gpg
11 | ${GPG_SECRET}
12 |
13 |
14 |
15 |
16 |
17 | ossrh
18 | ${OSSH_USER}
19 | ${OSSH_PASS}
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | # Licensed to the Apache Software Foundation (ASF) under one
2 | # or more contributor license agreements. See the NOTICE file
3 | # distributed with this work for additional information
4 | # regarding copyright ownership. The ASF licenses this file
5 | # to you under the Apache License, Version 2.0 (the
6 | # "License"); you may not use this file except in compliance
7 | # with the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 | wrapperVersion=3.3.2
18 | distributionType=only-script
19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
20 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.
4 |
5 | ## Pull Request Checklist
6 | 1) Create your branch from the main branch
7 | 2) Use [Semantic Commit Messages](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716)
8 | 3) Increase the version by using [Semantic Versioning](https://semver.org)
9 | 4) Ensure your changes are covered by tests
10 | 5) Follow the rules of [Clean Code](https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29) while coding
11 | 6) No reflection is used in the code
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🧬 Project Nano
2 |
3 | [//]: # ([![Build][build_shield]][build_link])
4 |
5 | [//]: # ([![Maintainable][maintainable_shield]][maintainable_link])
6 |
7 | [//]: # ([![Coverage][coverage_shield]][coverage_link])
8 | [![Issues][issues_shield]][issues_link]
9 | [![Commit][commit_shield]][commit_link]
10 | [![License][license_shield]][license_link]
11 | [![Central][central_shield]][central_link]
12 | [![Tag][tag_shield]][tag_link]
13 | [![Javadoc][javadoc_shield]][javadoc_link]
14 | [![Size][size_shield]][size_shield]
15 | ![Label][label_shield]
16 | ![Label][java_version]
17 |
18 | > [Introduction](#-introduction)
19 | > | [Core Concept](#-core-concept)
20 | > | [Mechanics](#-mechanics)
21 | > | [Components](#-components)
22 | > | [Getting Started](#-getting-started)
23 | > | [Build Nano](#-build-nano)
24 | > | [Benefits](#-benefits-of-nano)
25 |
26 | ## 🖼️ Introduction
27 |
28 | **Back to basics and forget about frameworks!**
29 |
30 | Nano is a lightweight concept which makes it easier for developer to write microservices in
31 | **functional, fluent, chaining, plain, modern java** with a nano footprint.
32 | Nano is also designed to be fully compilable with [GraalVM](https://www.graalvm.org) to create native executables.
33 | To enhance efficiency and performance, Nano utilizes non-blocking virtual threads from [Project Loom](https://jdk.java.net/loom/).
34 |
35 | ## 📐 Core Concept
36 |
37 | Nano handles threads for you and provides a basic construct for event driven architecture.
38 | It's providing a simple way to write microservices in a functional fluent and chaining style.
39 | **Objects are less needed** thanks to the underlying [TypeMap](https://github.com/YunaBraska/type-map).
40 | Nano provides full access to all internal components, resulting in very few private methods or fields.
41 |
42 | [Read more...](docs/info/concept/README.md)
43 |
44 | ## 📚 Components
45 |
46 | **All you need to know are few classes:**
47 | [Context](docs/context/README.md),
48 | [Events](docs/events/README.md),
49 | [Schedulers](docs/schedulers/README.md),
50 | [Services](docs/services/README.md)
51 |
52 | ```mermaid
53 | flowchart LR
54 | nano(((Nano))) --> context[Context]
55 | context --> events[Events]
56 | events --> services[Services]
57 | services --> schedulers[Schedulers]
58 |
59 | style nano fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
60 | style context fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
61 | style events fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
62 | style services fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
63 | style schedulers fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
64 | ```
65 |
66 | ## ⚙️ Mechanics
67 |
68 | * [Error Handling](docs/info/errorhandling/README.md)
69 | * [Registers](docs/registers/README.md) _(ConfigRegister, TypeConversionRegister, LogFormatRegister,
70 | EventChannelRegister)_
71 | * [Integrations](docs/integrations/README.md) _(🌱 Spring Boot, 🧑🚀 Micronaut, 🐸 Quarkus)_
72 | * [Code Examples](src/test/java/berlin/yuna/nano/examples)
73 |
74 | ## 📚 Getting Started
75 |
76 | Maven example
77 |
78 | ```xml
79 |
80 |
81 | org.nanonative
82 | nano
83 | 1.0.0
84 |
85 | ```
86 |
87 | Gradle example
88 |
89 | ```groovy
90 | dependencies {
91 | implementation 'org.nanonative:nano:1.0.0'
92 | }
93 | ```
94 |
95 | Simple Nano example with [HttpServer](docs/services/httpserver/README.md) _(a default service)_
96 |
97 | ```java
98 | public static void main(final String[] args) {
99 | // Start Nano with HttpServer
100 | final Nano app = new Nano(args, new HttpServer());
101 |
102 | // listen to /hello
103 | app.subscribeEvent(EVENT_HTTP_REQUEST, event -> event.payloadOpt(HttpObject.class)
104 | .filter(HttpObject::isMethodGet)
105 | .filter(request -> request.pathMatch("/hello"))
106 | .ifPresent(request -> request.response().body(Map.of("Hello", System.getProperty("user.name"))).respond(event)));
107 |
108 | // Override error handling for HTTP requests
109 | app.subscribeEvent(EVENT_APP_UNHANDLED, event -> event.payloadOpt(HttpObject.class).ifPresent(request ->
110 | request.response().body("Internal Server Error [" + event.error().getMessage() + "]").statusCode(500).respond(event)));
111 | }
112 | ```
113 |
114 | ## 🔨 Build Nano
115 |
116 | add the native-image profile to your `pom.xml` and run `mvn package -Pnative-image`
117 |
118 | ```xml
119 |
120 |
121 |
122 |
123 | org.graalvm.nativeimage
124 | native-image-maven-plugin
125 | 21.2.0
126 |
127 | ExampleApp
128 | de.yuna.berlin.nativeapp.helper.ExampleApp
129 |
130 |
131 | --no-fallback
132 |
133 | --no-server
134 |
135 | --initialize-at-build-time
136 |
137 | -H:IncludeResources=resources/config/.*
138 |
139 |
140 |
141 |
142 |
143 | native-image
144 |
145 | package
146 |
147 |
148 |
149 |
150 | ```
151 |
152 | ## ✨ Benefits of Nano:
153 |
154 | * 🧩 **Modular Design**: Nano's architecture is modular, making it easy to understand, extend, and maintain.
155 | * 🧵 **Concurrency Management**: Efficiently handle asynchronous tasks using advanced thread management.
156 | * 📡 **Event-Driven Architecture**: Robust event handling that simplifies communication between different parts of your
157 | application.
158 | * ⚙️ **Flexible Configuration**: Configure your application using environment variables, system properties, or
159 | command-line
160 | arguments.
161 | * 📊 **Robust Logging and Error Handling**: Integrated logging and comprehensive error handling mechanisms for reliable
162 | operation.
163 | * 🚀 **Scalable and Performant**: Designed with scalability and performance in mind to handle high-concurrency scenarios.
164 | * 🪶 **Lightweight & Fast**: Starts in milliseconds, uses ~10MB memory.
165 | * 🌿 **Pure Java, Pure Simplicity**: No reflections, no regex, no unnecessary magic.
166 | * ⚡ **GraalVM Ready**: For ahead-of-time compilation and faster startup.
167 | * 🔒 **Minimal Dependencies**: Reduces CVE risks and simplifies updates.
168 | * 🌊 **Fluent & Stateless**: Intuitive API design for easy readability and maintenance.
169 | * 🛠️ **Rapid Service Development**: Build real services in minutes.
170 |
171 | ## 🤝 Contributing
172 |
173 | Contributions to Nano are welcome! Please refer to our [Contribution Guidelines](CONTRIBUTING.md) for more information.
174 |
175 | ## 📜 License
176 |
177 | Nano is open-source software licensed under the [Apache license](LICENSE).
178 |
179 | ## 🙋 Support
180 |
181 | If you encounter any issues or have questions, please file an
182 | issue [here](https://github.com/nanonative/nano/issues/new/choose).
183 |
184 | ## 🌐 Stay Connected
185 |
186 | * [GitHub](https://github.com/NanoNative)
187 | * [X (aka Twitter)](https://twitter.com/YunaMorgenstern)
188 | * [Mastodon](https://hachyderm.io/@LunaFreyja)
189 | * [LinkedIn](https://www.linkedin.com/in/yuna-morgenstern-6662a5145/)
190 |
191 | 
192 |
193 |
194 | [build_shield]: https://github.com/nanonative/nano/workflows/MVN_RELEASE/badge.svg
195 |
196 | [build_link]: https://github.com/nanonative/nano/actions?query=workflow%3AMVN_RELEASE
197 |
198 | [maintainable_shield]: https://img.shields.io/codeclimate/maintainability/nanonative/nano?style=flat-square
199 |
200 | [maintainable_link]: https://codeclimate.com/github/nanonative/nano/maintainability
201 |
202 | [coverage_shield]: https://img.shields.io/codeclimate/coverage/nanonative/nano?style=flat-square
203 |
204 | [coverage_link]: https://codeclimate.com/github/nanonative/nano/test_coverage
205 |
206 | [issues_shield]: https://img.shields.io/github/issues/nanonative/nano?style=flat-square
207 |
208 | [issues_link]: https://github.com/nanonative/nano/issues/new/choose
209 |
210 | [commit_shield]: https://img.shields.io/github/last-commit/nanonative/nano?style=flat-square
211 |
212 | [commit_link]: https://github.com/nanonative/nano/commits/main
213 |
214 | [license_shield]: https://img.shields.io/github/license/nanonative/nano?style=flat-square
215 |
216 | [license_link]: https://github.com/nanonative/nano/blob/main/LICENSE
217 |
218 | [dependency_shield]: https://img.shields.io/librariesio/github/nanonative/nano?style=flat-square
219 |
220 | [dependency_link]: https://libraries.io/github/nanonative/nano
221 |
222 | [central_shield]: https://img.shields.io/maven-central/v/org.nanonative/nano?style=flat-square
223 |
224 | [central_link]:https://search.maven.org/artifact/org.nanonative/nano
225 |
226 | [tag_shield]: https://img.shields.io/github/v/tag/nanonative/nano?style=flat-square
227 |
228 | [tag_link]: https://github.com/nanonative/nano/releases
229 |
230 | [javadoc_shield]: https://javadoc.io/badge2/org.nanonative/nano/javadoc.svg?style=flat-square
231 |
232 | [javadoc_link]: https://javadoc.io/doc/org.nanonative/nano
233 |
234 | [size_shield]: https://img.shields.io/github/repo-size/nanonative/nano?style=flat-square
235 |
236 | [label_shield]: https://img.shields.io/badge/Yuna-QueenInside-blueviolet?style=flat-square
237 |
238 | [gitter_shield]: https://img.shields.io/gitter/room/nanonative/nano?style=flat-square
239 |
240 | [gitter_link]: https://gitter.im/nano/Lobby
241 |
242 | [java_version]: https://img.shields.io/badge/java-21-blueviolet?style=flat-square
243 |
244 |
245 |
--------------------------------------------------------------------------------
/docs/events/README.md:
--------------------------------------------------------------------------------
1 | > [Home](../../README.md) / [Components](../../README.md#-components)
2 |
3 | [Context](../context/README.md)
4 | | [**> Events <**](README.md)
5 | | [Schedulers](../schedulers/README.md)
6 | | [Services](../services/README.md)
7 |
8 | # Events
9 |
10 | [Events](../events/README.md) are the backbone of communication within the Nano Framework, to decoupled interaction between different parts of
11 | an application.
12 | They are a core API concept for using [Services](../services/README.md).
13 | See [Event.java](../../src/main/java/org/nanonative/nano/helper/event/model/Event.java)
14 |
15 | ```mermaid
16 | flowchart LR
17 | eventRegistry(((Event Registry))) --> channelId[channelId]
18 | channelId --> sendEvent[sendEvent]
19 | sendEvent --> eventListeners[EventListeners]
20 | sendEvent --> services[Services]
21 | services --> eventResponse[Event Response]
22 | eventListeners --> eventResponse[Event Response]
23 |
24 | style eventRegistry fill:#E3F2FD,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
25 | style channelId fill:#E3F2FD,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
26 | style sendEvent fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
27 | style eventListeners fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
28 | style services fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
29 | style eventResponse fill:#E3F2FD,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
30 | ```
31 |
32 | ## ChannelIds
33 |
34 | `ChannelIds` are globally, unique IDs to identify the right channel to send events into,
35 | They can be registered once with `ChannelIdRegister.registerChannelId("MY_EVENT_NAME");` -
36 | see [EventChanelRegister.java](../../src/main/java/org/nanonative/nano/helper/event/EventChannelRegister.java)
37 | and [DefaultEventChannel](../../src/main/java/org/nanonative/nano/helper/event/model/EventChannel.java)
38 |
39 | ## Sending Events
40 |
41 | [Events](../events/README.md) can be sent **synchronous**, **asynchronous**, **single cast** or **broadcast**.
42 |
43 | * synchronous (SingleCast)
44 | * `context.newEvent(channelId).payload(MyPayloadObject).send()`
45 | * asynchronous (SingleCast)
46 | * `context.newEvent(channelId).payload(MyPayloadObject).async(true).send()`
47 | * asynchronous with listener (SingleCast)
48 | * `context.newEvent(channelId).payload(MyPayloadObject).async(response -> myListener).send()`
49 |
50 | * synchronous (BroadCast)
51 | * `context.newEvent(channelId).payload(MyPayloadObject).broadcast(true).send()`
52 | * _broadcast will not stop at the first responding listener_
53 | * asynchronous (BroadCast)
54 | * `context.newEvent(channelId).payload(MyPayloadObject).broadcast(true).async(true).send()`
55 | * _broadcast will not stop at the first responding listener_
56 | * asynchronous with listener (SingleCast)
57 | * `context.newEvent(channelId).payload(MyPayloadObject).broadcast(true).async(true).send()`
58 | * _broadcast will not stop at the first responding listener_
59 |
60 | # Listening to Events
61 |
62 | Listeners can be easily registered with `context.subscribeEvent(channelId, event -> System.out.println(event))`.
63 | [Services](../services/README.md) don't need to subscribe or unsubscribe to [Events](../events/README.md) as they are
64 | managed and receive the
65 | [Events](../events/README.md) through the build
66 | in method `onEvent`
67 |
68 | ## Default Events
69 |
70 | | In 🔲 Out 🔳 | [Event](../events/README.md) | Payload | Response | Description |
71 | |--------------------|----------------------------------|-------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------|
72 | | 🔲 | `EVENT_APP_START` | `Nano` | `N/A` | Triggered when the Application is started |
73 | | 🔲 | `EVENT_APP_SHUTDOWN` | `null` | `N/A` | Triggered when the Application shuts down, can be also manually produced to shut down the Application |
74 | | 🔲 | `EVENT_APP_SERVICE_REGISTER` | `Service` | `N/A` | Triggered when a [Service](../services/README.md) is started |
75 | | 🔲 | `EVENT_APP_SERVICE_UNREGISTER` | `Service` | `N/A` | Triggered when a [Service](../services/README.md) is stopped |
76 | | 🔲 | `EVENT_APP_SCHEDULER_REGISTER` | `Scheduler` | `N/A` | Triggered when a [Scheduler](../schedulers/README.md) is started |
77 | | 🔲 | `EVENT_APP_SCHEDULER_UNREGISTER` | `Scheduler` | `N/A` | Triggered when a [Scheduler](../schedulers/README.md) is stopped |
78 | | 🔲 | `EVENT_APP_UNHANDLED` | `Unhandled`, `HttpObject`,... | `N/A` | Triggered when an unhandled error happened within the context |
79 | | 🔲 | `EVENT_APP_OOM` | `Double` | `N/A` | Triggered when the Application reached out of memory. When the event is not handled, the App will shutdown see config `app_oom_shutdown_threshold` |
80 | | 🔲 | `EVENT_APP_HEARTBEAT` | `Nano` | `N/A` | Send every 256ms |
81 | | 🔳 | `EVENT_CONFIG_CHANGE` | `TypeMap` | `N/A` | Used to change configs on the fly for services which supports it |
82 |
83 |
--------------------------------------------------------------------------------
/docs/info/concept/README.md:
--------------------------------------------------------------------------------
1 | > [Home](../../../README.md) / **[Concept](README.md)**
2 |
3 | # Concept
4 |
5 | Nano is a minimalist standalone library designed to facilitate the creation of microservices using plain, modern Java.
6 | Nano is a tool, not a framework, and it emphasizes simplicity, security, and efficiency.
7 |
8 | ### Modern and Fluent Design 🚀
9 |
10 | Nano leverages fluent chaining and functional programming styles to create a syntax that resembles a stateless scripting
11 | language. By avoiding annotations and other “black magic,” Nano maintains transparency and simplicity in its codebase.
12 | Fluent and chaining means, there are no `get` and `set` prefixes and no `void` returns for methods.
13 |
14 | ### No External Dependencies 🔒
15 |
16 | Nano is built without any foreign dependencies, ensuring a lean, secure library free from common vulnerabilities and
17 | excessive dependencies. This results in a smaller, faster, and more secure codebase. You only need to trust and know the
18 | license agreements of Nano.
19 |
20 | ### Minimal Resource Consumption 🌱
21 |
22 | Nano is engineered for a minimal environmental footprint, utilizing fewer resources and making garbage collection more
23 | efficient due to its functional programming style.
24 |
25 | ### Non-Blocking Virtual Threads 🧵
26 |
27 | Nano utilizes non-blocking virtual threads from [Project Loom](https://jdk.java.net/loom/) to enhance efficiency and
28 | performance. These threads maximize CPU utilization without blocking the main thread, eliminating the need for manual
29 | thread limit settings.
30 | Note that Nano cannot control Java’s built-in `ForkJoinPool` used for `java.util.concurrent` objects like streams.
31 | To optimize performance, it is recommended to set the Java property to something like
32 | this `-Djava.util.concurrent.ForkJoinPool.common.parallelism=100.` in case of high parallelism.
33 |
34 | ### GraalVM Compatibility ⚡
35 |
36 | Nano is fully compatible with [GraalVM](https://www.graalvm.org), allowing you to compile native executables that do not
37 | require a JVM to run. This feature is particularly useful in containerized and serverless environments.
38 | Nano avoids reflection and dynamic class loading, ensuring seamless [GraalVM](https://www.graalvm.org) integration
39 | without additional configuration.
40 |
41 | ### Extensible and Open 🪶
42 |
43 | All Nano functions and classes are `public` or `protected`, allowing developers to extend or modify the library as
44 | needed. This breaks the concept of immutable objects, but we think it's more important to be able to extend and modify
45 | Nano than closing it. Means, every developer is responsible for the own code!
46 | We still encourages contributions and improvements from the community.
47 |
48 | ### Modular Design 🧩
49 |
50 | Nano’s [Event](../../events/README.md) system enables decoupling of functions, plugin
51 | creation ([Services](../../services/README.md)), and function interception.
52 | For example, you can globally control and respond to every error that occurs, similar to a global `Controller Advice`.
53 | With that its also easy to change configurations on the fly.
54 | This modular design allows services, such as the built-in [HttpServer](../../services/httpserver/README.md) and
55 | [MetricService](../../services/metricservice/README.md), to operate independently while still being able to interact
56 | when started.
57 |
58 | ### Service-Based Architecture 📊
59 |
60 | ([Services](../../services/README.md)) in Nano function as plugins or extensions, executed only when explicitly added to
61 | Nano programmatically.
62 | This approach simplifies testing, as services and components can be tested independently without the need for mocking or
63 | stubbing.
64 | You execute only what you define, avoiding the pitfalls of auto-applying dependencies.
65 |
66 | ### Flexible Object Mapping 🔄
67 |
68 | Nano’s built-in `TypeConverter` eliminates the need for custom objects by enabling easy conversion of `JSON`, `XML`, and
69 | other simple Java objects.
70 | For example, HTTP requests can be converted to `TypeInfo`, `TypeMap` or `TypeList`, which lazily convert fields to
71 | the requested type. _See [TypeMap](https://github.com/YunaBraska/type-map) for more information._
72 | If an object cannot be converted, it is straightforward to register a custom type conversion.
73 | These [TypeMaps](https://github.com/YunaBraska/type-map) and TypeLists are used extensively, such as in events and the context.
74 |
75 | ### Configuration Management ⚙️
76 |
77 | Nano uses a [Context](../../context/README.md) object to manage logging, tracing and configurations.
78 | Nano reads property files and profiled properties which all end up in the [Context](../../context/README.md) Object.
79 | The properties can be converted to the required types as needed.
80 | This eliminates the need for custom configuration objects.
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/docs/info/errorhandling/README.md:
--------------------------------------------------------------------------------
1 | > [Home](../../../README.md) / **[ErrorHandling](README.md)**
2 |
3 | # Error Handling
4 |
5 | Error handling is pretty straight forward in Nano.
6 | All errors are [Events](../../events/README.md) which are logged automatically with the [LogService](../../services/logger/README.md)
7 | from the caller [Context](../../context/README.md).
8 | These [Events](../../events/README.md) are send to the `EVENT_APP_UNHANDLED` channel and can be caught or intercepted.
9 |
10 | ## Error Channel
11 |
12 | The channel `EVENT_APP_UNHANDLED` is used for all errors and also unhandled http events.
13 | _See [HttpServer](../../services/httpserver/README.md) for more information._
14 | Therefore, its necessary to filter the right events to catch. Error events usually have a non nullable `error` property.
15 |
16 | ## Handle Error
17 |
18 | To handle an error, you can subscribe to the `EVENT_APP_UNHANDLED` channel and filter the events by the `error`
19 | property.
20 | `event.acknowledge()` is optional to stop further processing. _(Cough vs Intercept)_
21 |
22 | ```java
23 | public static void main(final String[] args) {
24 | final Context context = new Nano(args).context(ErrorHandling.class);
25 |
26 | // Listen to exceptions
27 | context.subscribeEvent(EVENT_APP_UNHANDLED, event -> {
28 | // Print error message
29 | event.context().warn(() -> "Caught event [{}] with error [{}] ", event.nameOrg(), event.error().getMessage());
30 | event.acknowledge(); // Set exception as handled (prevent further processing)
31 | });
32 |
33 | // Throw an exception
34 | context.run(() -> {
35 | throw new RuntimeException("Test Exception");
36 | });
37 | }
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/integrations/README.md:
--------------------------------------------------------------------------------
1 | > [Home](../../README.md) / [**Integrations**](README.md)
2 |
3 | [Spring Boot](#-nano-in-spring-boot)
4 | | [Micronaut](#-nano-in-micronaut)
5 | | [Quarkus](#-nano-in-quarkus).
6 |
7 | # Integrations
8 |
9 | Nano is fully standalone and can be integrated into various frameworks and libraries.
10 | This section provides examples of how to integrate Nano into
11 |
12 | ## 🌱 Nano in Spring boot
13 |
14 | * Run Nano as Bean
15 |
16 | ```java
17 |
18 | @Configuration
19 | public class NanoConfiguration {
20 |
21 | @Bean
22 | public Nano nanoInstance() {
23 | // Initialize your Nano instance with the desired services
24 | return new Nano(); // Optionally add your services and configurations here
25 | }
26 | }
27 | ```
28 |
29 | * Use Nano in a Service
30 |
31 | ```java
32 |
33 | @Service
34 | public class SomeService {
35 |
36 | private final Nano nano;
37 |
38 | @Autowired
39 | public SomeService(final Nano nano) {
40 | this.nano = nano;
41 | // Use Nano instance as needed
42 | }
43 | }
44 | ```
45 |
46 | Nano has a graceful shutdown by itself, but it could be useful to trigger it from a Spring bean.
47 |
48 | * Graceful shutdown using `DisposableBean`
49 |
50 | ```java
51 |
52 | @Component
53 | public class NanoManager implements DisposableBean {
54 |
55 | private final Nano nano;
56 |
57 | public NanoManager(final Nano nano) {
58 | this.nano = nano;
59 | }
60 |
61 | @Override
62 | public void destroy() {
63 | nano.stop(); // Trigger Nano's shutdown process
64 | }
65 | }
66 | ```
67 |
68 | * Graceful shutdown using `@PreDestroy` annotation
69 |
70 | ```java
71 |
72 | @Component
73 | public class NanoManager {
74 |
75 | private final Nano nano;
76 |
77 | public NanoManager(final Nano nano) {
78 | this.nano = nano;
79 | }
80 |
81 | @PreDestroy
82 | public void onDestroy() {
83 | nano.stop(); // Trigger Nano's shutdown process
84 | }
85 | }
86 | ```
87 |
88 | ## 🧑🚀 Nano in Micronaut
89 |
90 | * Define the Nano Bean
91 |
92 | ```java
93 |
94 | @Factory
95 | public class NanoFactory {
96 |
97 | @Singleton
98 | public Nano nanoInstance() {
99 | // Initialize your Nano instance with desired services
100 | return new Nano(); // Optionally add services and configurations here
101 | }
102 | }
103 | ```
104 |
105 | * Use Nano in Your Application
106 |
107 | ```java
108 |
109 | @Singleton
110 | public class SomeService {
111 |
112 | private final Nano nano;
113 |
114 | public SomeService(final Nano nano) {
115 | this.nano = nano;
116 | // Use the Nano instance as needed
117 | }
118 | }
119 | ```
120 |
121 | * Graceful shutdown using `@ServerShutdownEvent`
122 |
123 | ```java
124 |
125 | @Singleton
126 | public class NanoManager implements ApplicationEventListener {
127 |
128 | private final Nano nano;
129 |
130 | public NanoManager(final Nano nano) {
131 | this.nano = nano;
132 | }
133 |
134 | @Override
135 | public void onApplicationEvent(final ServerShutdownEvent event) {
136 | nano.stop(); // Trigger Nano's shutdown process
137 | }
138 | }
139 | ```
140 |
141 | ## 🐸 Nano in Quarkus
142 |
143 | * Define the Nano Producer
144 |
145 | ```java
146 |
147 | @ApplicationScoped
148 | public class NanoProducer {
149 |
150 | @Produces
151 | public Nano produceNano() {
152 | // Initialize your Nano instance with the desired services
153 | return new Nano(); // Optionally add your services and configurations here
154 | }
155 | }
156 | ```
157 |
--------------------------------------------------------------------------------
/docs/registers/README.md:
--------------------------------------------------------------------------------
1 | > [Home](../../README.md) / **[Registers](README.md)**
2 |
3 | # Registers
4 |
5 | Nano comes with a set of registers that are used to add custom functionality to internal components.
6 | It's recommended to use the register in `static` blocks to ensure that they are only executed on need. Like when the
7 | class is used.
8 |
9 | ### ConfigRegister
10 |
11 | The `ConfigRegister` is used to register custom configuration values. This register is non functional and mostly used
12 | for documentation purposes like the help menu. The config keys are usually separated by `_` and written in lowercase.
13 | This ensures a common naming convention which is compatible in environments like env variables and as parameters.
14 |
15 | **Usage:**
16 |
17 | ```java
18 | static {
19 | // Register a config key
20 | String key = ConfigRegister.registerConfig("my_config_key", "my description");
21 |
22 | // Getting a config description
23 | String description = ConfigRegister.configDescriptionOf("my_config_key");
24 |
25 | // Getting all configs
26 | Map allConfigs = CONFIG_KEYS;
27 | }
28 | ```
29 |
30 | ### EventChannelRegister
31 |
32 | The `EventChannelRegister` is used to register custom [Event](../events/README.md) channels to send or
33 | subscribe [events](../events/README.md) to.
34 | the registration is needed to create unique channel ids for the [Event](../events/README.md) bus. These ids are faster
35 | than using `String` ids
36 |
37 | **Usage:**
38 |
39 | ```java
40 | static {
41 | // Register a channel
42 | int MY_EVENT_CHANNEL_ID = EventChannelRegister.registerChannelId("my_channel_name");
43 |
44 | // Getting a channel name by id
45 | String myChanelName = EventChannelRegister.eventNameOf(MY_EVENT_CHANNEL_ID);
46 |
47 | // Getting a channelId by name
48 | int MY_EVENT_CHANNEL_ID = EventChannelRegister.eventIdOf("my_channel_name");
49 |
50 | // checking if a channel is registered
51 | boolean isChannelAvailable = ConfigRegister.isChannelIdAvailable("my_config_key");
52 | }
53 | ```
54 |
55 | ### LogFormatRegister
56 |
57 | This register is used to register custom log formats. Default formats are `console` and `json`.
58 | The [LogService](../services/logger/README.md) is still under construction. The functionality might change in the future.
59 | Simply use the default log Formatter interface of java `java.util.logging.Formatter`.
60 |
61 | **Usage:**
62 |
63 | ```java
64 | static {
65 | // Register a log formatter
66 | LogFormatRegister.registerLogFormatter("xml", new XmlLogFormatter());
67 |
68 | // Getting a log formatter by name
69 | Formatter jsonFormatter = LogFormatRegister.getLogFormatter("json");
70 | }
71 | ```
72 |
73 | ### TypeConversionRegister
74 |
75 | The `TypeConversionRegister` is used to register custom type converters. It's the core of Nano.
76 | These type conversion are used in the [Config/Context](../context/README.md), [Event](../events/README.md)
77 | Cache, [HttpServer](../services/httpserver/README.md) request & responses and everything which
78 | uses `TypeMap`, `TypeList` or `TypeInfo`. _See [TypeMap](https://github.com/YunaBraska/type-map) for more information._
79 |
80 | **Usage:**
81 |
82 | ```java
83 | import berlin.yuna.typemap.logic.TypeConverter;
84 |
85 | static {
86 | // Register type conversion from String to LogLevel
87 | registerTypeConvert(String.class, LogLevel.class, LogLevel::nanoLogLevelOf);
88 |
89 | // Register type conversion from LogLevel to String
90 | registerTypeConvert(LogLevel.class, String.class, Enum::name);
91 |
92 | // Manual type conversion
93 | TypeConverter.convertObj("INFO", LogLevel.class);
94 | }
95 | ```
96 |
--------------------------------------------------------------------------------
/docs/schedulers/README.md:
--------------------------------------------------------------------------------
1 | > [Home](../../README.md) / [Components](../../README.md#-components)
2 |
3 | [Context](../context/README.md)
4 | | [Events](../events/README.md)
5 | | [**> Schedulers <**](README.md)
6 | | [Services](../services/README.md)
7 |
8 | # Schedulers
9 |
10 | [Schedulers](../schedulers/README.md) are managed functions which run in the background.
11 |
12 | ```mermaid
13 | flowchart LR
14 | context(((Context))) --> schedulers[Schedulers] --> function[Custom Function]
15 |
16 | style context fill:#E3F2FD,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
17 | style schedulers fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
18 | style function fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
19 | ```
20 |
21 | ## Examples
22 |
23 | * Run once with delay (128ms)
24 | * `context.run(() -> System.out.println("Scheduled"), 128, MILLISECONDS)`
25 | * Run periodically (evey 256ms) with initial delay (128ms)
26 | * `context.run(() -> System.out.println("Scheduled"), 128, 256, MILLISECONDS)`
27 | * Run at specific time (8:00:00)
28 | * `context.run(System.out.println("Scheduled"), LocalTime.of(8,0,0))`
29 | * Run at specific time (8:00:00) on specific day (Monday)
30 | * `context.run(() -> {}, LocalTime.of(8,0,0), DayOfWeek.MONDAY)`
31 |
--------------------------------------------------------------------------------
/docs/services/README.md:
--------------------------------------------------------------------------------
1 | > [Home](../../README.md) / [Components](../../README.md#-components)
2 |
3 | [Context](../context/README.md)
4 | | [Events](../events/README.md)
5 | | [Schedulers](../schedulers/README.md)
6 | | [**> Services <**](README.md)
7 |
8 | # Services
9 |
10 | [Services](../services/README.md) are extensions for Nano which are independent managed programs that are running in the
11 | background.
12 | They are usually designed to be accessed by [Events](../events/README.md).
13 | Nano has default [Services](../services/README.md)
14 | like [HttpServer](httpserver/README.md), [MetricService](metricservice/README.md), [LogService](../services/logger/README.md)
15 |
16 | ## Start Services
17 |
18 | * `new Nano(new MetricService(), new HttpServer(), new HttpClient())` - [Services](../services/README.md) will start with
19 | Nano Startup
20 | * `context.run(new HttpServer())` - Service start
21 |
22 | ```mermaid
23 | flowchart TD
24 | services(((Services))) -.-> metricService[MetricService]
25 | services -.-> httpServer[HttpServer]
26 | metricService <--> events[Event]
27 | httpServer <--> events[Event]
28 | events[Event] <--> function[Custom Function]
29 |
30 | style services fill:#E3F2FD,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
31 | style events fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
32 | style httpServer fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
33 | style metricService fill:#90CAF9,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
34 | style function fill:#E3F2FD,stroke:#1565C0,stroke-width:1px,color:#1A237E,rx:2%,ry:2%
35 | ```
36 |
--------------------------------------------------------------------------------
/docs/services/httpclient/README.md:
--------------------------------------------------------------------------------
1 | > [Home](../../../README.md)
2 | > / [Components](../../../README.md#-components)
3 | > / [Services](../../services/README.md)
4 | > / [**HttpClient**](README.md)
5 |
6 | * [Configuration](#configuration)
7 | * [Events](#events)
8 |
9 | # Http Client
10 |
11 | Is a default [Services](../../services/README.md) of Nano which is responsible for sending basic HTTP requests.
12 |
13 | ## Usage
14 |
15 | ### Start Http Service
16 |
17 | A) As startup [Service](../../services/README.md): `new Nano(new HttpClient())`
18 |
19 | B) Contextual `context.run(new HttpClient())` - this way its possible to provide a custom configuration.
20 |
21 | ### Intercept HTTP Requests
22 |
23 | The Event listener are executed in order of subscription.
24 | This makes it possible to define change data before sending the HTTP request.
25 |
26 | ```java
27 | public static void main(final String[] args) {
28 | final Nano app = new Nano(args, new HttpClient());
29 |
30 | app.context(MyClass.class).newEvent(EVENT_SEND_HTTP, () -> new Httpobject().methodType(GET).path("http://localhost:8080/hello").body("Hello World")).send();
31 |
32 | // Add Token to request
33 | app.subscribeEvent(EVENT_HTTP_REQUEST, event ->
34 | event.payloadOpt(HttpObject.class).ifPresent(request -> request.header("Authorization", "myCustomToken"))
35 | );
36 | }
37 | ```
38 |
39 | ### Send HTTP Requests
40 |
41 | ```java
42 | import org.nanonative.nano.services.http.HttpClient;
43 |
44 | public static void main(final String[] args) {
45 | final Context context = new Nano(args, new HttpClient()).context(MyClass.class);
46 |
47 | // send request via event
48 | final HttpObject response1 = context.sendEventR(EVENT_SEND_HTTP, () -> new HttpObject()
49 | .methodType(GET)
50 | .path("http://localhost:8080/hello")
51 | .body("Hello World")
52 | ).response(HttpObject.class);
53 |
54 | // send request via context
55 | final HttpObject response2 = new HttpObject()
56 | .methodType(GET)
57 | .path("http://localhost:8080/hello")
58 | .body("Hello World")
59 | .send(context);
60 |
61 | // send request manually
62 | final HttpObject response3 = new HttpClient().send(new HttpObject()
63 | .methodType(GET)
64 | .path("http://localhost:8080/hello")
65 | .body("Hello World")
66 | );
67 | }
68 | ```
69 |
70 | ## Configuration
71 |
72 | | [Config](../../context/README.md#configuration) | Type | Default | Description |
73 | |-------------------------------------------------|-----------|-------------------------------|-----------------------------------------------------|
74 | | `app_service_http_version` | `Integer` | `2` | (HttpClient) Http Version 1 or 2 |
75 | | `app_service_http_max_retries` | `Integer` | `3` | (HttpClient) Maximum number of retries |
76 | | `app_service_http_con_timeoutMs` | `Integer` | `5000` | (HttpClient) Connection timeout in milliseconds |
77 | | `app_service_http_read_timeoutMs` | `Integer` | `10000` | (HttpClient) Read timeout in milliseconds |
78 | | `app_service_http_follow_redirects` | `Boolean` | `true` | (HttpClient) Follow redirects |
79 |
80 | ## Events
81 |
82 | | In 🔲 Out 🔳 | [Event](../../events/README.md) | Payload | Response | Description |
83 | |--------------------|---------------------------------|--------------|--------------|-----------------------|
84 | | 🔳 | `EVENT_SEND_HTTP` | `HttpObject` | `HttpObject` | Sending a HttpRequest |
85 |
86 |
--------------------------------------------------------------------------------
/docs/services/httpserver/README.md:
--------------------------------------------------------------------------------
1 | > [Home](../../../README.md)
2 | > / [Components](../../../README.md#-components)
3 | > / [Services](../../services/README.md)
4 | > / [**HttpServer**](README.md)
5 |
6 | * [Usage](#usage)
7 | * [Start HTTP Service](#start-http-service)
8 | * [Handle HTTP Requests](#handle-http-requests)
9 | * [Configuration](#configuration)
10 | * [Events](#events)
11 |
12 | # Http Service
13 |
14 | Is a default [Services](../../services/README.md) of Nano which is responsible for handling basic HTTP requests.
15 | Each request is processed in its own Thread.
16 | Support for Https/SSL is coming soon.
17 |
18 | ## Usage
19 |
20 | ### Start Http Service
21 |
22 | A) As startup [Service](../../services/README.md): `new Nano(new HttpServer())`
23 |
24 | B) Contextual `context.run(new HttpServer())` - this way its possible to provide a custom configuration.
25 |
26 | ### Handle HTTP Requests
27 |
28 | The Event listener are executed in order of subscription.
29 | This makes it possible to define authorization rules before the actual request is processed.
30 |
31 | ```java
32 | public static void main(final String[] args) {
33 | final Nano app = new Nano(args, new HttpServer());
34 |
35 | // Authorization
36 | app.subscribeEvent(EVENT_HTTP_REQUEST, RestEndpoint::authorize);
37 |
38 | // Response
39 | app.subscribeEvent(EVENT_HTTP_REQUEST, RestEndpoint::helloWorldController);
40 |
41 | // Error handling
42 | app.subscribeEvent(EVENT_APP_UNHANDLED, RestEndpoint::controllerAdvice);
43 | }
44 |
45 | private static void helloWorldController(final Event event) {
46 | event.payloadOpt(HttpObject.class)
47 | .filter(HttpObject::isMethodGet)
48 | .filter(request -> request.pathMatch("/hello"))
49 | .ifPresent(request -> request.response().body(Map.of("Hello", System.getProperty("user.name"))).respond(event));
50 | }
51 |
52 | private static void authorize(final Event event) {
53 | event.payloadOpt(HttpObject.class)
54 | .filter(request -> request.pathMatch("/hello/**"))
55 | .filter(request -> !"mySecretToken".equals(request.authToken()))
56 | .ifPresent(request -> request.response().body(Map.of("message", "You are unauthorized")).statusCode(401).respond(event));
57 | }
58 |
59 | private static void controllerAdvice(final Event event) {
60 | event.payloadOpt(HttpObject.class).ifPresent(request ->
61 | request.response().body("Internal Server Error [" + event.error().getMessage() + "]").statusCode(500).respond(event));
62 | }
63 | ```
64 |
65 | ## Configuration
66 |
67 | | [Config](../../context/README.md#configuration) | Type | Default | Description |
68 | |-------------------------------------------------|-----------|-------------------------------|-----------------------------------------------------|
69 | | `app_service_http_port ` | `Integer` | `8080`, `8081`, ... (dynamic) | (HttpServer) Port |
70 | | `app_service_http_client` | `Boolean` | `false` | (HttpClient) If the HttpClient should start as well |
71 |
72 | ## Events
73 |
74 | | In 🔲 Out 🔳 | [Event](../../events/README.md) | Payload | Response | Description |
75 | |--------------------|---------------------------------|--------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
76 | | 🔲 | `EVENT_HTTP_REQUEST` | `HttpObject` | `HttpObject` | Triggered when an HTTP request is received. If a response is returned for this event, it is sent back to the client. |
77 | | 🔲 | `EVENT_HTTP_REQUEST_UNHANDLED` | `HttpObject` | `HttpObject` | Triggered when an HTTP request is received but not handled. If a response is returned for this event, it is sent back to the client. Else client will receive a `404 |
78 | | 🔲 | `EVENT_APP_UNHANDLED` | `HttpObject` | `HttpObject` | Triggered when an exception occurs while handling an HTTP request. If a response is returned for this event, it is sent back to the client. Else client will receive a `500` |
79 | | 🔲 | `EVENT_HTTP_REQUEST` | `HttpObject` | `HttpObject` | Listening for HTTP request |
80 |
81 |
--------------------------------------------------------------------------------
/docs/services/logger/README.md:
--------------------------------------------------------------------------------
1 | > [Home](../../../README.md)
2 | > / [Components](../../../README.md#-components)
3 | > / [Services](../../services/README.md)
4 | > / [**Logger**](README.md)
5 |
6 | # LogService
7 |
8 | The [LogService](../logger/README.md) is a simple wrapper around the build in `System.out.print` which comes with predefined log formats `console`
9 | and `json`. The javaLogger is not work concurrently. But `Level`, `LogRecord` and `Formatter` are supported.
10 | This service is starting automatically if no other LogService is provided.
11 |
12 | ## Placeholder
13 |
14 | The logger supports placeholders in the message string. The placeholders are replaced by the arguments passed to the
15 | logger.
16 |
17 | * `{}` and `%s` is replaced by the argument at the same index
18 | * `{0}` is replaced by the argument at the specified index
19 |
20 | ## Log Formatter
21 |
22 | The [LogService](../logger/README.md) supports two log formatters at default:
23 |
24 | * `console` - The console formatter logs the message to the console.
25 | * Example: `context.info(() -> "Hello {}", "World")`
26 | * Output: `[2024-11-11 11:11:11.111] [DEBUG] [Nano] - Hello World`
27 | * `json` - The json formatter logs the message as json to the console.
28 | * Example: `context.debug(() -> "Hello {}", "World")`
29 | Output:
30 | `{"Hello":"World", "level":"DEBUG","logger":"Nano","message":"Hello World","timestamp":"2024-11-11 11:11:11.111"}`
31 |
32 | ## Custom Log Formatter
33 |
34 | Custom log formatters can be registered by using `LogFormatRegister.registerLogFormatter(Name, Formatter)` - (
35 | java.util.logging.Formatter)
36 |
37 | ## Custom LogService
38 |
39 | The default Logger can be overwritten by providing a custom `Service` which extends the `LogService` e.g.
40 | `new Nano(new CustomLogger())`
41 |
--------------------------------------------------------------------------------
/docs/services/metricservice/README.md:
--------------------------------------------------------------------------------
1 | > [Home](../../../README.md)
2 | > / [Components](../../../README.md#-components)
3 | > / [Services](../../services/README.md)
4 | > / [**HttpServer**](README.md)
5 |
6 | * [Usage](#usage)
7 | * [Start Metric Service](#start-metric-service)
8 | * [Create Custom Metrics](#create-custom-metrics)
9 | * [Configuration](#configuration)
10 | * [Events](#events)
11 |
12 | # Metric Service
13 |
14 | Is a default [Services](../../services/README.md) of Nano which is responsible for collecting metrics.
15 | This service solves only basic metrics.
16 | Currently, there is no mechanism to push metrics to other applications.
17 | The standard and best practice is to use a dedicated metric collector like Prometheus and poll for metrics.
18 | Same as for tracking network traffic.
19 | This way ensures, that microservice will stay small, simple without putting unnecessary complexity into it.
20 | However, it is possible to extend or wrap the service with a custom implementation. E.g. with
21 | a [Scheduler](../../schedulers/README.md) and the [HttpClient](../httpserver/README.md#send-http-requests).
22 | The [MetricService](README.md) provides simple methods to get the metrics in the format
23 | of `Influx`, `Dynamo`, `Wavefront` and `Prometheus`.
24 |
25 | ## Usage
26 |
27 | ### Start Metric Service
28 |
29 | A) As startup [Service](../../services/README.md): `new Nano(new MetricService())`
30 |
31 | B) Contextual `context.run(new MetricService())` - this way its possible to provide a custom configuration.
32 |
33 | ### Metric Endpoints
34 |
35 | To get the metrics via HTTP, its necessary to also start
36 | the [HttpServer](../httpserver/README.md) `new Nano(new MetricService(), new HttpServer())`.
37 |
38 | The following endpoints are available:
39 |
40 | * `/metrics/influx`
41 | * `/metrics/dynamo`
42 | * `/metrics/wavefront`
43 | * `/metrics/prometheus`
44 |
45 | ### Create Custom Metrics
46 |
47 | ```java
48 | public static void main(final String[] args) {
49 | final Context context = new Nano(args, new HttpServer()).context(MyClass.class);
50 |
51 | // create counter
52 | context.newEvent(EVENT_METRIC_UPDATE).payload(() -> new MetricUpdate(COUNTER, "my.counter.key", 130624, metricTags)).send();
53 | // create gauge
54 | context.newEvent(EVENT_METRIC_UPDATE).payload(() -> new MetricUpdate(GAUGE, "my.gauge.key", 200888, metricTags)).send();
55 | // start timer
56 | context.newEvent(EVENT_METRIC_UPDATE).payload(() -> new MetricUpdate(TIMER_START, "my.timer.key", null, metricTags)).send();
57 | // end timer
58 | context.newEvent(EVENT_METRIC_UPDATE).payload(() -> new MetricUpdate(TIMER_END, "my.timer.key", null, metricTags)).send();
59 | }
60 | ```
61 |
62 | ## Configuration
63 |
64 | | [Config](../../context/README.md#configuration) | Type | Default | Description |
65 | |-------------------------------------------------|----------|-----------------------|------------------------------------|
66 | | `app_service_metrics_base_url` | `String` | `/metrics` | Base path for all metric endpoints |
67 | | `app_service_influx_metrics_url` | `String` | `/metrics/influx` | Custom path for Influx |
68 | | `app_service_dynamo_metrics_url` | `String` | `/metrics/dynamo` | Custom path for Dynamo |
69 | | `app_service_prometheus_metrics_url` | `String` | `/metrics/prometheus` | Custom path for prometheus |
70 | | `app_service_wavefront_metrics_url` | `String` | `/metrics/wavefront` | Custom path for Wavefront |
71 |
72 | ## Events
73 |
74 | | In 🔲 Out 🔳 | [Event](../../events/README.md) | Payload | Response | Description |
75 | |--------------------|---------------------------------|----------------|--------------|--------------------------------------------|
76 | | 🔲 | `EVENT_METRIC_UPDATE` | `MetricUpdate` | `true` | Sets or updates specific metric |
77 | | 🔲 | `EVENT_APP_HEARTBEAT` | `-` | `true` | (Internal usage) Updates system metrics |
78 | | 🔲 | `EVENT_HTTP_REQUEST` | `HttpObject` | `HttpObject` | (Internal usage) provides metric endpoints |
79 |
80 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | <# : batch portion
2 | @REM ----------------------------------------------------------------------------
3 | @REM Licensed to the Apache Software Foundation (ASF) under one
4 | @REM or more contributor license agreements. See the NOTICE file
5 | @REM distributed with this work for additional information
6 | @REM regarding copyright ownership. The ASF licenses this file
7 | @REM to you under the Apache License, Version 2.0 (the
8 | @REM "License"); you may not use this file except in compliance
9 | @REM with the License. You may obtain a copy of the License at
10 | @REM
11 | @REM http://www.apache.org/licenses/LICENSE-2.0
12 | @REM
13 | @REM Unless required by applicable law or agreed to in writing,
14 | @REM software distributed under the License is distributed on an
15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | @REM KIND, either express or implied. See the License for the
17 | @REM specific language governing permissions and limitations
18 | @REM under the License.
19 | @REM ----------------------------------------------------------------------------
20 |
21 | @REM ----------------------------------------------------------------------------
22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2
23 | @REM
24 | @REM Optional ENV vars
25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution
26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
28 | @REM ----------------------------------------------------------------------------
29 |
30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
31 | @SET __MVNW_CMD__=
32 | @SET __MVNW_ERROR__=
33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
34 | @SET PSModulePath=
35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
37 | )
38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
39 | @SET __MVNW_PSMODULEP_SAVE=
40 | @SET __MVNW_ARG0_NAME__=
41 | @SET MVNW_USERNAME=
42 | @SET MVNW_PASSWORD=
43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
44 | @echo Cannot start maven from wrapper >&2 && exit /b 1
45 | @GOTO :EOF
46 | : end batch / begin powershell #>
47 |
48 | $ErrorActionPreference = "Stop"
49 | if ($env:MVNW_VERBOSE -eq "true") {
50 | $VerbosePreference = "Continue"
51 | }
52 |
53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
55 | if (!$distributionUrl) {
56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
57 | }
58 |
59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
60 | "maven-mvnd-*" {
61 | $USE_MVND = $true
62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
63 | $MVN_CMD = "mvnd.cmd"
64 | break
65 | }
66 | default {
67 | $USE_MVND = $false
68 | $MVN_CMD = $script -replace '^mvnw','mvn'
69 | break
70 | }
71 | }
72 |
73 | # apply MVNW_REPOURL and calculate MAVEN_HOME
74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
75 | if ($env:MVNW_REPOURL) {
76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
78 | }
79 | $distributionUrlName = $distributionUrl -replace '^.*/',''
80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
82 | if ($env:MAVEN_USER_HOME) {
83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
84 | }
85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
87 |
88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
91 | exit $?
92 | }
93 |
94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
96 | }
97 |
98 | # prepare tmp dir
99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
102 | trap {
103 | if ($TMP_DOWNLOAD_DIR.Exists) {
104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
106 | }
107 | }
108 |
109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
110 |
111 | # Download and Install Apache Maven
112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
113 | Write-Verbose "Downloading from: $distributionUrl"
114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
115 |
116 | $webclient = New-Object System.Net.WebClient
117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
119 | }
120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
122 |
123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file
124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
125 | if ($distributionSha256Sum) {
126 | if ($USE_MVND) {
127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
128 | }
129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
132 | }
133 | }
134 |
135 | # unzip and move
136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
138 | try {
139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
140 | } catch {
141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
142 | Write-Error "fail to move MAVEN_HOME"
143 | }
144 | } finally {
145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
147 | }
148 |
149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
150 |
--------------------------------------------------------------------------------
/src/main/java/org/nanonative/nano/core/NanoServices.java:
--------------------------------------------------------------------------------
1 | package org.nanonative.nano.core;
2 |
3 | import org.nanonative.nano.core.model.Context;
4 | import org.nanonative.nano.core.model.Service;
5 | import org.nanonative.nano.helper.ExRunnable;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.Map;
10 | import java.util.Optional;
11 | import java.util.concurrent.CopyOnWriteArrayList;
12 |
13 | import static java.util.Collections.emptyList;
14 | import static java.util.Collections.unmodifiableList;
15 | import static java.util.Optional.ofNullable;
16 |
17 | /**
18 | * The abstract base class for {@link Nano} framework providing {@link Service} handling functionalities.
19 | *
20 | * @param The type of the {@link NanoServices} implementation, used for method chaining.
21 | */
22 | @SuppressWarnings({"unused", "UnusedReturnValue"})
23 | public abstract class NanoServices> extends NanoThreads {
24 |
25 | protected final List services;
26 |
27 | /**
28 | * Initializes {@link NanoServices} with configurations and command-line arguments.
29 | *
30 | * @param config Configuration parameters for the {@link NanoServices} instance.
31 | * @param args Command-line arguments passed during the application start.
32 | */
33 | protected NanoServices(final Map
35 | *
36 | * Note: The formatter also handles exceptions by appending the stack trace to the log entry, should an exception be thrown during execution.
37 | *
38 | */
39 | public class LogFormatterConsole extends Formatter {
40 | protected final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
41 | protected final int paddingLogLevel = Arrays.stream(LogLevel.values()).map(LogLevel::toString).mapToInt(String::length).max().orElse(5);
42 |
43 | /**
44 | * Format a LogRecord into a string representation.
45 | *
46 | * @param logRecord the log record to be formatted.
47 | * @return a formatted log string.
48 | */
49 | @SuppressWarnings("java:S3457")
50 | @Override
51 | public String format(final LogRecord logRecord) {
52 | final StringBuilder formattedLog = new StringBuilder();
53 | formattedLog.append("[")
54 | .append(dateFormat.format(new Date(logRecord.getMillis())))
55 | .append("] [")
56 | .append(String.format("%-" + paddingLogLevel + "s", nanoLogLevelOf(logRecord.getLevel())))
57 | .append("] [")
58 | .append(formatLoggerName(logRecord))
59 | .append("] - ")
60 | .append(applyCustomFormat(formatMessage(logRecord), logRecord.getParameters()))
61 | .append(NanoUtils.LINE_SEPARATOR);
62 | if (logRecord.getThrown() != null) {
63 | formattedLog.append(convertObj(logRecord.getThrown(), String.class)).append(NanoUtils.LINE_SEPARATOR);
64 | }
65 | return formattedLog.toString();
66 | }
67 |
68 | @SuppressWarnings("java:S3457")
69 | protected static String formatLoggerName(final LogRecord logRecord) {
70 | final int dot = logRecord.getLoggerName().lastIndexOf(".");
71 | return String.format("%-" + MAX_LOG_NAME_LENGTH.get() + "s", (dot != -1 ? logRecord.getLoggerName().substring(dot + 1) : logRecord.getLoggerName()));
72 | }
73 |
74 | /**
75 | * Replacing placeholders with parameters.
76 | *
77 | * @param message the message to be formatted.
78 | * @param params the parameters for the message.
79 | * @return a string with the formatted message.
80 | */
81 | protected static String applyCustomFormat(final String message, final Object... params) {
82 | if (message != null && params != null && params.length > 0) {
83 | final String result = message.replace("{}", "%s");
84 | return String.format(result, params);
85 | }
86 | return message;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/org/nanonative/nano/services/logging/LogFormatterJson.java:
--------------------------------------------------------------------------------
1 | package org.nanonative.nano.services.logging;
2 |
3 | import java.text.SimpleDateFormat;
4 | import java.util.Date;
5 | import java.util.Map;
6 | import java.util.TreeMap;
7 | import java.util.logging.Formatter;
8 | import java.util.logging.LogRecord;
9 | import java.util.regex.Matcher;
10 | import java.util.regex.Pattern;
11 | import java.util.stream.Collectors;
12 |
13 | import static berlin.yuna.typemap.logic.TypeConverter.convertObj;
14 | import static org.nanonative.nano.helper.NanoUtils.LINE_SEPARATOR;
15 | import static org.nanonative.nano.services.logging.model.LogLevel.nanoLogLevelOf;
16 |
17 | /**
18 | * A log formatter that outputs log records in JSON format.
19 | *
20 | * This formatter structures log messages into JSON objects, which is beneficial for systems that ingest log data for analysis,
21 | * allowing for easy parsing and structured querying of log data.
22 | *
27 | * In this example, 'successCount' replaces the first '{}' placeholder, 'failureCount' replaces the '%s' placeholder and 'ignoreCount' replaces the last [{2}] placeholder.
28 | * The formatter will convert the log into a JSON line.
29 | * The map's keys and values becoming part of the JSON structure.
30 | * A key value map is not really needed as the keys and values from the messages itself becomes a part of the JSON structure as well. This makes it easier to switch between console and json logging.
31 | *
32 | *
33 | * Additionally, it supports automatic key-value extraction from the log message itself, enabling inline parameterization.
34 | * The extracted keys and values are also included in the JSON output.
35 | *
36 | *
37 | * The formatter handles exceptions by appending a "error" field with the exception message to the JSON log entry.
38 | *
39 | */
40 | public class LogFormatterJson extends Formatter {
41 | protected final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
42 | protected static final Pattern MESSAGE_KEY_VALUE_PATTERN = Pattern.compile("(\\w+):?\\s*\\[\\{\\}]");
43 |
44 | /**
45 | * Formats a log record into a JSON string.
46 | *
47 | * @param logRecord The log record to format.
48 | * @return The log record formatted as a JSON string.
49 | */
50 | @Override
51 | public String format(final LogRecord logRecord) {
52 | final Object[] params = logRecord.getParameters();
53 | final Map jsonMap = new TreeMap<>();
54 | final String message = LogFormatterConsole.applyCustomFormat(logRecord.getMessage(), params);
55 | final int dot = logRecord.getLoggerName().lastIndexOf(".");
56 | extractKeyValuesFromMessage(jsonMap, logRecord.getMessage(), params);
57 | addJsonEntries(jsonMap, params);
58 |
59 | putEntry(jsonMap, "message", message);
60 | putEntry(jsonMap, "timestamp", dateFormat.format(new Date(logRecord.getMillis())));
61 | putEntry(jsonMap, "level", nanoLogLevelOf(logRecord.getLevel()));
62 | putEntry(jsonMap, "package", dot != -1 ? logRecord.getLoggerName().substring(0, dot) : "");
63 | putEntry(jsonMap, "logger", dot != -1 ? logRecord.getLoggerName().substring(dot + 1) : logRecord.getLoggerName());
64 | if (logRecord.getThrown() != null) {
65 | putEntry(jsonMap, "error", jsonEscape(convertObj(logRecord.getThrown(), String.class)));
66 | }
67 | return "{" + jsonMap.entrySet().stream().map(entry -> "\"" + entry.getKey() + "\":\"" + entry.getValue() + "\"").collect(Collectors.joining(",")) + "}" + LINE_SEPARATOR;
68 | }
69 |
70 | /**
71 | * Extracts key-value pairs from the message and stores them in a map.
72 | *
73 | * @param jsonMap The map to store the key-value pairs.
74 | * @param message The log message.
75 | * @param params The parameters for the log message.
76 | */
77 | protected void extractKeyValuesFromMessage(final Map jsonMap, final String message, final Object[] params) {
78 | final Matcher matcher = MESSAGE_KEY_VALUE_PATTERN.matcher(message);
79 |
80 | int index = 0;
81 | while (matcher.find() && params != null && index < params.length) {
82 | putEntry(jsonMap, matcher.group(1), params[index]);
83 | index++;
84 | }
85 | }
86 |
87 | /**
88 | * Adds additional key-value pairs to the map.
89 | *
90 | * @param jsonMap The map to store the key-value pairs.
91 | * @param params The parameters for the log message.
92 | */
93 | protected void addJsonEntries(final Map jsonMap, final Object[] params) {
94 | if (params != null) {
95 | for (final Object param : params) {
96 | if (param instanceof final Map, ?> map) {
97 | for (final Map.Entry, ?> entry : map.entrySet()) {
98 | putEntry(jsonMap, entry.getKey(), entry.getValue());
99 | }
100 | }
101 | }
102 | }
103 | }
104 |
105 | /**
106 | * Adds escaped and converted key-value pairs to the map.
107 | *
108 | * @param jsonMap The map to store the key-value pairs.
109 | * @param key key
110 | * @param value value
111 | */
112 | protected void putEntry(final Map jsonMap, final Object key, final Object value) {
113 | jsonMap.put(jsonEscape(convertObj(key, String.class)), jsonEscape(convertObj(value, String.class)));
114 | }
115 |
116 | /**
117 | * Escapes special characters for JSON compatibility.
118 | *
119 | * @param value The object to escape.
120 | * @return The escaped string.
121 | */
122 | protected String jsonEscape(final Object value) {
123 | if (value == null) return null;
124 | final String strValue = value.toString();
125 | final StringBuilder sb = new StringBuilder();
126 | for (int i = 0; i < strValue.length(); i++) {
127 | final char c = strValue.charAt(i);
128 | switch (c) {
129 | case '"':
130 | sb.append("\\\"");
131 | break;
132 | case '\\':
133 | sb.append("\\\\");
134 | break;
135 | case '/':
136 | sb.append("\\/");
137 | break;
138 | case '\b':
139 | sb.append("\\b");
140 | break;
141 | case '\f':
142 | sb.append("\\f");
143 | break;
144 | case '\n':
145 | sb.append("\\n");
146 | break;
147 | case '\r':
148 | sb.append("\\r");
149 | break;
150 | case '\t':
151 | sb.append("\\t");
152 | break;
153 | default:
154 | sb.append(c);
155 | }
156 | }
157 | return sb.toString();
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/main/java/org/nanonative/nano/services/logging/LogService.java:
--------------------------------------------------------------------------------
1 | package org.nanonative.nano.services.logging;
2 |
3 | import berlin.yuna.typemap.model.LinkedTypeMap;
4 | import berlin.yuna.typemap.model.TypeMapI;
5 | import org.nanonative.nano.core.model.Service;
6 | import org.nanonative.nano.helper.event.model.Event;
7 | import org.nanonative.nano.services.logging.model.LogLevel;
8 |
9 | import java.util.Arrays;
10 | import java.util.List;
11 | import java.util.concurrent.atomic.AtomicInteger;
12 | import java.util.function.Supplier;
13 | import java.util.logging.Formatter;
14 | import java.util.logging.Level;
15 | import java.util.logging.LogRecord;
16 |
17 | import static org.nanonative.nano.helper.config.ConfigRegister.registerConfig;
18 | import static org.nanonative.nano.helper.event.EventChannelRegister.registerChannelId;
19 |
20 | public class LogService extends Service {
21 |
22 | // CONFIG
23 | public static final String CONFIG_LOG_LEVEL = registerConfig("app_log_level", "Log level for the application (see " + LogLevel.class.getSimpleName() + ")");
24 | public static final String CONFIG_LOG_FORMATTER = registerConfig("app_log_formatter", "Log formatter (see " + LogFormatRegister.class.getSimpleName() + ")");
25 | public static final String CONFIG_LOG_EXCLUDE_PATTERNS = registerConfig("app_log_excludes", "Exclude patterns for logger names");
26 |
27 | // CHANNEL
28 | public static final int EVENT_LOGGING = registerChannelId("EVENT_LOGGING");
29 |
30 | public static final AtomicInteger MAX_LOG_NAME_LENGTH = new AtomicInteger(10);
31 | protected List excludePatterns;
32 | protected Formatter logFormatter = new LogFormatterConsole();
33 | protected Level level = Level.FINE;
34 |
35 | @Override
36 | public void start() {
37 | // nothing to do
38 | }
39 |
40 | @Override
41 | public void stop() {
42 | // nothing to do
43 | }
44 |
45 | @Override
46 | public Object onFailure(final Event error) {
47 | return null;
48 | }
49 |
50 | @Override
51 | public void onEvent(final Event event) {
52 | event.filter(this::isLoggable).ifPresent(
53 | event1 -> context.run(() -> event1.ifPresentAck(EVENT_LOGGING, LogRecord.class, this::log))
54 | );
55 | }
56 |
57 | @Override
58 | public void configure(final TypeMapI> configs, final TypeMapI> merged) {
59 | merged.asOpt(LogLevel.class, CONFIG_LOG_LEVEL).map(LogLevel::toJavaLogLevel).ifPresent(this::level);
60 | merged.asOpt(Formatter.class, CONFIG_LOG_FORMATTER).ifPresent(this::formatter);
61 | excludePatterns = merged.asStringOpt(CONFIG_LOG_EXCLUDE_PATTERNS).map(patterns -> Arrays.stream(patterns.split(",")).map(String::trim).toList()).orElseGet(List::of);
62 | }
63 |
64 | public synchronized LogService level(final Level level) {
65 | this.level = level;
66 | return this;
67 | }
68 |
69 | public Level level() {
70 | return level;
71 | }
72 |
73 | public synchronized LogService formatter(final Formatter formatter) {
74 | this.logFormatter = formatter;
75 | return this;
76 | }
77 |
78 | public Formatter formatter() {
79 | return this.logFormatter;
80 | }
81 |
82 | public void log(final Supplier logRecord) {
83 | context.run(() -> log(logRecord.get()));
84 | }
85 |
86 | @SuppressWarnings("SameReturnValue")
87 | protected boolean log(final LogRecord logRecord) {
88 | context.run(() -> {
89 | final String formattedMessage = logFormatter.format(logRecord);
90 | if (logRecord.getLevel().intValue() < Level.WARNING.intValue()) {
91 | System.out.print(formattedMessage);
92 | } else {
93 | System.err.print(formattedMessage);
94 | }
95 | });
96 | return true;
97 | }
98 |
99 | private boolean isLoggable(final Event event) {
100 | return event.channelId() == EVENT_LOGGING && event.asOpt(Level.class, "level")
101 | .filter(level -> level.intValue() > this.level.intValue())
102 | .map(level -> event.asString("name"))
103 | .filter(name -> excludePatterns == null || excludePatterns.stream().noneMatch(name::contains))
104 | .map(name -> event.acknowledge())
105 | .isPresent();
106 | }
107 |
108 | @Override
109 | public String toString() {
110 | return new LinkedTypeMap()
111 | .putR("name", this.getClass().getSimpleName())
112 | .putR("level", level)
113 | .putR("isReady", isReady.get())
114 | .putR("context", context.size())
115 | .putR("excludePatterns", excludePatterns != null ? excludePatterns.size() : 0)
116 | .putR("logFormatter", logFormatter.getClass().getSimpleName())
117 | .putR("MAX_LOG_NAME_LENGTH", MAX_LOG_NAME_LENGTH.get())
118 | .toJson();
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/org/nanonative/nano/services/logging/model/LogLevel.java:
--------------------------------------------------------------------------------
1 | package org.nanonative.nano.services.logging.model;
2 |
3 | import org.nanonative.nano.helper.NanoUtils;
4 |
5 | import java.util.Arrays;
6 | import java.util.logging.Level;
7 |
8 | public enum LogLevel {
9 | OFF(Level.OFF),
10 | FATAL(Level.SEVERE),
11 | ERROR(Level.SEVERE),
12 | WARN(Level.WARNING),
13 | INFO(Level.INFO),
14 | DEBUG(Level.FINE),
15 | TRACE(Level.FINER),
16 | ALL(Level.ALL); // OR FINEST?
17 |
18 | private final Level javaLogLevel;
19 |
20 | LogLevel(final Level javaLogLevel) {
21 | this.javaLogLevel = javaLogLevel;
22 | }
23 |
24 | public java.util.logging.Level toJavaLogLevel() {
25 | return javaLogLevel;
26 | }
27 |
28 | public static LogLevel nanoLogLevelOf(final Level level) {
29 | return Arrays.stream(LogLevel.values()).filter(simpleLogLevel -> simpleLogLevel.javaLogLevel == level).findFirst().orElse(OFF);
30 |
31 | }
32 |
33 | public static LogLevel nanoLogLevelOf(final String level) {
34 | if (NanoUtils.hasText(level)) {
35 | // Nano log level
36 | for (final LogLevel logLevel : LogLevel.values()) {
37 | if (logLevel.toString().equalsIgnoreCase(level)) {
38 | return logLevel;
39 | }
40 | }
41 |
42 | // Java log level
43 | for (final LogLevel logLevel : LogLevel.values()) {
44 | if (logLevel.javaLogLevel.toString().equalsIgnoreCase(level)) {
45 | return logLevel;
46 | }
47 | }
48 | }
49 | return ALL;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/org/nanonative/nano/services/metric/model/MetricCache.java:
--------------------------------------------------------------------------------
1 | package org.nanonative.nano.services.metric.model;
2 |
3 | import java.util.Map;
4 | import java.util.TreeMap;
5 | import java.util.concurrent.ConcurrentHashMap;
6 | import java.util.concurrent.atomic.AtomicLong;
7 | import java.util.stream.Collectors;
8 |
9 | import static java.util.Collections.emptyMap;
10 | import static java.util.Optional.ofNullable;
11 |
12 | @SuppressWarnings({"UnusedReturnValue"})
13 | public class MetricCache {
14 |
15 | private final ConcurrentHashMap> counters = new ConcurrentHashMap<>();
16 | private final ConcurrentHashMap> gauges = new ConcurrentHashMap<>();
17 | private final ConcurrentHashMap> timers = new ConcurrentHashMap<>();
18 |
19 | public record Metric(T value, TreeMap tags, String metricName) {
20 | }
21 |
22 | public Map> counters() {
23 | return counters;
24 | }
25 |
26 | public Map> gauges() {
27 | return gauges;
28 | }
29 |
30 | public Map> timers() {
31 | return timers;
32 | }
33 |
34 | public Map> sorted() {
35 | final TreeMap> result = new TreeMap<>();
36 | result.putAll(counters);
37 | result.putAll(gauges);
38 | result.putAll(timers);
39 | return result;
40 | }
41 |
42 | public MetricCache counterIncrement(final String name) {
43 | return counterIncrement(name, null);
44 | }
45 |
46 | public MetricCache counterIncrement(final String name, final Map tags) {
47 | if (name != null) {
48 | final String id = sanitizeMetricName(name);
49 | final TreeMap sortedTags = new TreeMap<>(tags != null ? tags : emptyMap());
50 | counters.computeIfAbsent(tags == null ? id : generateUniqueKey(id, sortedTags), key -> new Metric<>(new AtomicLong(), sortedTags, id)).value.incrementAndGet();
51 | }
52 | return this;
53 | }
54 |
55 | public long counter(final String name) {
56 | return counter(name, null);
57 | }
58 |
59 | public long counter(final String name, final Map tags) {
60 | final String id = sanitizeMetricName(name);
61 | return ofNullable(counters.get(tags == null ? id : generateUniqueKey(id, new TreeMap<>(tags)))).map(Metric::value).map(AtomicLong::get).orElse(-1L);
62 | }
63 |
64 | public MetricCache gaugeSet(final String name, final double value) {
65 | return gaugeSet(name, value, null);
66 | }
67 |
68 | public MetricCache gaugeSet(final String name, final double value, final Map tags) {
69 | if (name != null && value > -1) {
70 | final String id = sanitizeMetricName(name);
71 | final TreeMap sortedTags = new TreeMap<>(tags != null ? tags : emptyMap());
72 | gauges.put(tags == null ? id : generateUniqueKey(id, sortedTags), new Metric<>(value, sortedTags, id));
73 | }
74 | return this;
75 | }
76 |
77 | public double gauge(final String name) {
78 | return gauge(name, null);
79 | }
80 |
81 | public double gauge(final String name, final Map tags) {
82 | final String id = sanitizeMetricName(name);
83 | return ofNullable(gauges.get(tags == null ? id : generateUniqueKey(id, new TreeMap<>(tags)))).map(Metric::value).orElse(-1d);
84 | }
85 |
86 | public MetricCache timerStart(final String name) {
87 | return timerStart(name, null);
88 | }
89 |
90 | public MetricCache timerStart(final String name, final Map tags) {
91 | if (name != null) {
92 | final String id = sanitizeMetricName(name);
93 | final TreeMap sortedTags = new TreeMap<>(tags != null ? tags : emptyMap());
94 | timers.put(tags == null ? id : generateUniqueKey(id, sortedTags), new Metric<>(System.currentTimeMillis(), sortedTags, id));
95 | }
96 | return this;
97 | }
98 |
99 | public MetricCache timerStop(final String name) {
100 | return timerStop(name, null);
101 | }
102 |
103 | public MetricCache timerStop(final String name, final Map tags) {
104 | if (name != null) {
105 | final String id = sanitizeMetricName(name);
106 | final TreeMap sortedTags = new TreeMap<>(tags != null ? tags : emptyMap());
107 | timers.computeIfPresent(tags == null ? id : generateUniqueKey(id, sortedTags), (key, metric) -> new Metric<>(System.currentTimeMillis() - metric.value, sortedTags, id));
108 | }
109 | return this;
110 | }
111 |
112 | public long timer(final String name) {
113 | return timer(name, null);
114 | }
115 |
116 | public long timer(final String name, final Map tags) {
117 | final String id = sanitizeMetricName(name);
118 | return ofNullable(timers.get(tags == null ? id : generateUniqueKey(id, new TreeMap<>(tags)))).map(Metric::value).orElse(-1L);
119 | }
120 |
121 | // Adjustments for metric formatting methods to use metric.metricName instead of the unique key
122 | public String prometheus() {
123 | final StringBuilder result = new StringBuilder();
124 | sorted().forEach((id, metric) -> result.append(formatPrometheusMetric(metric)));
125 | return result.toString();
126 | }
127 |
128 | // Example adjustment for the InfluxDB format
129 | public String influx() {
130 | final StringBuilder sb = new StringBuilder();
131 | sorted().forEach((id, metric) -> sb.append(metric.metricName()).append(formatInfluxTags(metric.tags())).append(" value=").append(metric.value() instanceof final AtomicLong val ? val.get() : metric.value()).append("\n"));
132 | return sb.toString();
133 | }
134 |
135 | public String dynatrace() {
136 | final StringBuilder sb = new StringBuilder();
137 | sorted().forEach((id, metric) -> sb.append(formatDynatraceMetric(metric)));
138 | return sb.toString();
139 | }
140 |
141 | public String wavefront() {
142 | final StringBuilder sb = new StringBuilder();
143 | sorted().forEach((id, metric) -> sb.append(formatWavefrontMetric(metric)));
144 | return sb.toString();
145 | }
146 |
147 | // Utility method for generating unique keys remains unchanged
148 | public String generateUniqueKey(final String name, final Map tags) {
149 | final String tagString = tags.entrySet().stream()
150 | .sorted(Map.Entry.comparingByKey())
151 | .map(entry -> entry.getKey() + "=" + entry.getValue())
152 | .reduce((t1, t2) -> t1 + "&" + t2).orElse("");
153 | return name + "{" + tagString + "}";
154 | }
155 |
156 | public String sanitizeMetricName(final String name) {
157 | return name == null ? "UNKNOWN.METRIC" : name.replaceAll("[^a-zA-Z0-9.]", ".").replace("..", ".").replaceAll("^\\.|\\.$", "");
158 | }
159 |
160 | // Adjusted formatting methods to utilize metric.metricName
161 | private String formatPrometheusMetric(final Metric> metric) {
162 | final String tagsString = metric.tags.entrySet().stream()
163 | .map(entry -> entry.getKey() + "=\"" + entry.getValue() + "\"")
164 | .reduce((t1, t2) -> t1 + "," + t2)
165 | .map(tags -> "{" + tags + "}").orElse("");
166 | return metric.metricName.replace(".", "_") + tagsString + " " + metric.value + "\n";
167 | }
168 |
169 | private String formatInfluxTags(final Map tags) {
170 | final StringBuilder tagsBuilder = new StringBuilder();
171 | tags.forEach((key, value) -> tagsBuilder.append(",").append(key).append("=").append(value));
172 | return tagsBuilder.toString();
173 | }
174 |
175 | private String formatDynatraceMetric(final Metric> metric) {
176 | // Adjusting for correct tag formatting in Dynatrace metrics
177 | final String dimensions = metric.tags.entrySet().stream()
178 | .map(entry -> entry.getKey() + "=" + entry.getValue())
179 | .collect(Collectors.joining(","));
180 | return metric.metricName + "," + dimensions + " " + metric.value + "\n";
181 | }
182 |
183 | private String formatWavefrontMetric(final Metric> metric) {
184 | // Wavefront format corrected for tag placement
185 | final String tags = metric.tags.entrySet().stream()
186 | .map(entry -> entry.getKey() + "=" + entry.getValue())
187 | .collect(Collectors.joining(" "));
188 | return metric.metricName + " " + metric.value + " source=nano " + tags + "\n";
189 | }
190 |
191 | @Override
192 | public String toString() {
193 | return this.getClass().getSimpleName() + "{" +
194 | "counters=" + counters.size() +
195 | ", gauges=" + gauges.size() +
196 | ", timers=" + timers.size() +
197 | '}';
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/main/java/org/nanonative/nano/services/metric/model/MetricType.java:
--------------------------------------------------------------------------------
1 | package org.nanonative.nano.services.metric.model;
2 |
3 | public enum MetricType {
4 |
5 | COUNTER,
6 | GAUGE,
7 | TIMER_START,
8 | TIMER_END,
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/org/nanonative/nano/services/metric/model/MetricUpdate.java:
--------------------------------------------------------------------------------
1 | package org.nanonative.nano.services.metric.model;
2 |
3 | import java.util.Map;
4 |
5 | public record MetricUpdate(MetricType type, String name, Number value, Map tags) {
6 | }
7 |
--------------------------------------------------------------------------------
/src/test/java/org/nanonative/nano/core/config/TestConfig.java:
--------------------------------------------------------------------------------
1 | package org.nanonative.nano.core.config;
2 |
3 | import org.nanonative.nano.services.logging.model.LogLevel;
4 |
5 | import java.util.concurrent.CountDownLatch;
6 | import java.util.concurrent.atomic.AtomicReference;
7 | import java.util.function.Supplier;
8 |
9 | import static java.util.Optional.ofNullable;
10 | import static java.util.concurrent.TimeUnit.MILLISECONDS;
11 | import static org.nanonative.nano.helper.NanoUtils.tryExecute;
12 |
13 | public class TestConfig {
14 |
15 | /**
16 | * Defines the log level for testing purposes, allowing for easy adjustment during debugging.
17 | * This can be particularly useful when trying to isolate or identify specific issues within tests.
18 | */
19 | public static final LogLevel TEST_LOG_LEVEL = LogLevel.WARN;
20 |
21 | /**
22 | * Specifies the number of times tests should be repeated to ensure concurrency reliability. This setting aims to strike a balance between thorough testing and practical execution times.
23 | * It's advised to maintain this value around 100 repeats. Higher values might affect the reliability of timing-sensitive assertions due to the varying capabilities of different testing environments.
24 | *