• Stars
    star
    1,334
  • Rank 35,222 (Top 0.7 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 8 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Willow is a powerful, yet lightweight logging library written in Swift.

Willow

Build Status CocoaPods Compatible Carthage Compatible Platform

Willow is a powerful, yet lightweight logging library written in Swift.

Features

  • Default Log Levels
  • Custom Log Levels
  • Simple Logging Functions using Closures
  • Configurable Synchronous or Asynchronous Execution
  • Thread-Safe Logging Output (No Log Mangling)
  • Custom Writers through Dependency Injection
  • Custom Modifiers through Dependency Injection per Writer
  • Supports Multiple Simultaneous Writers
  • Shared Loggers Between Frameworks
  • Shared Locks or Queues Between Multiple Loggers
  • Comprehensive Unit Test Coverage
  • Complete Documentation

Requirements

  • iOS 9.0+ / Mac OS X 10.11+ / tvOS 9.0+ / watchOS 2.0+
  • Xcode 9.3+
  • Swift 4.1+

Migration Guides

Communication

  • Need help? Open an issue.
  • Have a feature request? Open an issue.
  • Find a bug? Open an issue.
  • Want to contribute? Fork the repo and submit a pull request.

Installation

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:

[sudo] gem install cocoapods

CocoaPods 1.3+ is required.

To integrate Willow into your project, specify it in your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '11.0'
use_frameworks!

pod 'Willow', '~> 5.0'

Then, run the following command:

$ pod install

Carthage

Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.

You can install Carthage with Homebrew using the following command:

$ brew update
$ brew install carthage

To integrate Willow into your Xcode project using Carthage, specify it in your Cartfile:

github "Nike-Inc/Willow" ~> 5.0

Run carthage update to build the framework and drag the built Willow.framework into your Xcode project.

Swift Package Manager

The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. It is in early development, but Willow does support its use on supported platforms.

Once you have your Swift package set up, adding Willow as a dependency is as easy as adding it to the dependencies value of your Package.swift.

dependencies: [
    .package(url: "https://github.com/Nike-Inc/Willow.git", majorVersion: 5)
]

Usage

Creating a Logger

import Willow

let defaultLogger = Logger(logLevels: [.all], writers: [ConsoleWriter()])

The Logger initializer takes three parameters to customize the behavior of the logger instance.

  • logLevels: [LogLevel] - The log message levels that should be processed. Messages that don't match the current log level are not processed.

  • writers: [LogWriter] - The array of writers to write to. Writers can be used to log output to a specific destination such as the console, a file, or an external service.

  • executionMethod: ExecutionMethod = .synchronous(lock: NSRecursiveLock()) - The execution method used when writing messages.

Logger objects can only be customized during initialization. If you need to change a Logger at runtime, it is advised to create an additional logger with a custom configuration to fit your needs. It is perfectly acceptable to have many different Logger instances running simultaneously.

Thread Safety

The print function does not guarantee that the String parameter will be fully logged to the console. If two print calls are happening simultaneously from two different queues (threads), the messages can get mangled, or intertwined. Willow guarantees that messages are completely finished writing before starting on the next one.

It is important to note that by creating multiple Logger instances, you can potentially lose the guarantee of thread-safe logging. If you want to use multiple Logger instances, you should create a NSRecursiveLock or DispatchQueue that is shared between both configurations. For more info, see the Advanced Usage section.

Logging Messages and String Messages

Willow can log two different types of objects: Messages and Strings.

Log Messages

Messages are structured data with a name and a dictionary of attributes. Willow declares the LogMessage protocol which frameworks and applications can use as the basis for concrete implementations. Messages are a good choice if you want to provide context information along with the log text (e.g. routing log information to an external system like New Relic).

enum Message: LogMessage {
    case requestStarted(url: URL)
    case requestCompleted(url: URL, response: HTTPURLResponse)

    var name: String {
        switch self {
        case .requestStarted:   return "Request started"
        case .requestCompleted: return "Request completed"
        }
    }

    var attributes: [String: Any] {
        switch self {
        case let .requestStarted(url):
            return ["url": url]

        case let .requestCompleted(url, response):
            return ["url": url, "response_code": response.statusCode]
        }
    }
}

let url = URL(string: "https://httpbin.org/get")!

log.debug(Message.requestStarted(url: url))
log.info(Message.requestStarted(url: url))
log.event(Message.requestStarted(url: url))
log.warn(Message.requestStarted(url: url))
log.error(Message.requestStarted(url: url))

Log Message Strings

Log message strings are just String instances with no additional data.

let url = URL(string: "https://httpbin.org/get")!

log.debugMessage("Request Started: \(url)")
log.infoMessage("Request Started: \(url)")
log.eventMessage("Request Started: \(url)")
log.warnMessage("Request Started: \(url)")
log.errorMessage("Request Started: \(url)")

The log message string APIs have the Message suffix on the end to avoid ambiguity with the log message APIs. The multi-line escaping closure APIs collide without the suffix.

Logging Messages with Closures

The logging syntax of Willow was optimized to make logging as lightweight and easy to remember as possible. Developers should be able to focus on the task at hand and not remembering how to write a log message.

Single Line Closures

let log = Logger()

// Option 1
log.debugMessage("Debug Message")    // Debug Message
log.infoMessage("Info Message")      // Info Message
log.eventMessage("Event Message")    // Event Message
log.warnMessage("Warn Message")      // Warn Message
log.errorMessage("Error Message")    // Error Message

// or

// Option 2
log.debugMessage { "Debug Message" } // Debug Message
log.infoMessage { "Info Message" }   // Info Message
log.eventMessage { "Event Message" } // Event Message
log.warnMessage { "Warn Message" }   // Warn Message
log.errorMessage { "Error Message" } // Error Message

Both of these approaches are equivalent. The first set of APIs accept autoclosures and the second set accept closures.

Feel free to use whichever syntax you prefer for your project. Also, by default, only the String returned by the closure will be logged. See the Log Modifiers section for more information about customizing log message formats.

The reason both sets of APIs use closures to extract the log message is performance.

There are some VERY important performance considerations when designing a logging solution that are described in more detail in the Closure Performance section.

Multi-Line Closures

Logging a message is easy, but knowing when to add the logic necessary to build a log message and tune it for performance can be a bit tricky. We want to make sure logic is encapsulated and very performant. Willow log level closures allow you to cleanly wrap all the logic to build up the message.

log.debugMessage {
    // First let's run a giant for loop to collect some info
    // Now let's scan through the results to get some aggregate values
    // Now I need to format the data
    return "Computed Data Value: \(dataValue)"
}

log.infoMessage {
    let countriesString = ",".join(countriesArray)
    return "Countries: \(countriesString)"
}

Unlike the Single Line Closures, the Multi-Line Closures require a return declaration.

Closure Performance

Willow works exclusively with logging closures to ensure the maximum performance in all situations. Closures defer the execution of all the logic inside the closure until absolutely necessary, including the string evaluation itself. In cases where the Logger instance is disabled, log execution time was reduced by 97% over the traditional log message methods taking a String parameter. Additionally, the overhead for creating a closure was measured at 1% over the traditional method making it negligible. In summary, closures allow Willow to be extremely performant in all situations.

Disabling a Logger

The Logger class has an enabled property to allow you to completely disable logging. This can be helpful for turning off specific Logger objects at the app level, or more commonly to disable logging in a third-party library.

let log = Logger()
log.enabled = false

// No log messages will get sent to the registered Writers

log.enabled = true

// We're back in business...

Synchronous and Asynchronous Logging

Logging can greatly affect the runtime performance of your application or library. Willow makes it very easy to log messages synchronously or asynchronously. You can define this behavior when creating the LoggerConfiguration for your Logger instance.

let queue = DispatchQueue(label: "serial.queue", qos: .utility)
let log = Logger(logLevels: [.all], writers: [ConsoleWriter()], executionMethod: .asynchronous(queue: queue))

Synchronous Logging

Synchronous logging is very helpful when you are developing your application or library. The log operation will be completed before executing the next line of code. This can be very useful when stepping through the debugger. The downside is that this can seriously affect performance if logging on the main thread.

Asynchronous Logging

Asynchronous logging should be used for deployment builds of your application or library. This will offload the logging operations to a separate dispatch queue that will not affect the performance of the main thread. This allows you to still capture logs in the manner that the Logger is configured, yet not affect the performance of the main thread operations.

These are large generalizations about the typical use cases for one approach versus the other. Before making a final decision about which approach to use when, you should really break down your use case in detail.

Log Writers

Writing log messages to various locations is an essential feature of any robust logging library. This is made possible in Willow through the LogWriter protocol.

public protocol LogWriter {
    func writeMessage(_ message: String, logLevel: LogLevel)
    func writeMessage(_ message: Message, logLevel: LogLevel)
}

Again, this is an extremely lightweight design to allow for ultimate flexibility. As long as your LogWriter classes conform, you can do anything with those log messages that you want. You could write the message to the console, append it to a file, send it to a server, etc. Here's a quick look at a simple write that writes to the console.

open class ConsoleWriter: LogMessageWriter {
    open func writeMessage(_ message: String, logLevel: LogLevel) {
        print(message)
    }

    open func writeMessage(_ message: LogMessage, logLevel: LogLevel) {
        let message = "\(message.name): \(message.attributes)"
        print(message)
    }
}

Log Modifiers

Log message customization is something that Willow specializes in. Some devs want to add a prefix to their library output, some want different timestamp formats, some even want emoji! There's no way to predict all the types of custom formatting teams are going to want to use. This is where LogModifier objects come in.

public protocol LogModifier {
    func modifyMessage(_ message: String, with logLevel: LogLevel) -> String
}

The LogModifier protocol has only a single API. It receives the message and logLevel and returns a newly formatted String. This is about as flexible as you can get.

As an added layer of convenience, writers intending to output strings (e.g. writing to the console, files, etc.) can conform to the LogModifierWritier protocol. The LogModifierWriter protocol adds an array of LogModifier objects to the LogWriter that can be applied to the message before it is output using the modifyMessage(_:logLevel) API in the extension.

Let's walk through a simple example for adding a prefix to a logger for the debug and info log levels.

class PrefixModifier: LogModifier {
    func modifyMessage(_ message: String, with logLevel: Logger.LogLevel) -> String {
        return "[Willow] \(message)"
    }
}

let prefixModifiers = [PrefixModifier()]
let writers = [ConsoleWriter(modifiers: prefixModifiers)]
let log = Logger(logLevels: [.debug, .info], writers: writers)

To apply modifiers consistently to strings, LogModifierWriter objects should call modifyMessage(_:logLevel) to create a new string based on the original string with all the modifiers applied in order.

open func writeMessage(_ message: String, logLevel: LogLevel) {
    let message = modifyMessage(message, logLevel: logLevel)
    print(message)
}

Multiple Modifiers

Multiple LogModifier objects can be stacked together onto a single log level to perform multiple actions. Let's walk through using the TimestampModifier (prefixes the message with a timestamp) in combination with an EmojiModifier.

class EmojiModifier: LogModifier {
    func modifyMessage(_ message: String, with logLevel: LogLevel) -> String {
        return "🚀🚀🚀 \(message)"
    }
}

let writers: = [ConsoleWriter(modifiers: [EmojiModifier(), TimestampModifier()])]
let log = Logger(logLevels: [.all], writers: writers)

Willow doesn't have any hard limits on the total number of LogModifier objects that can be applied to a single log level. Just keep in mind that performance is key.

The default ConsoleWriter will execute the modifiers in the same order they were added into the Array. In the previous example, Willow would log a much different message if the TimestampModifier was inserted before the EmojiModifier.

OSLog

The OSLogWriter class allows you to use the os_log APIs within the Willow system. In order to use it, all you need to do is to create the LogModifier instance and add it to the Logger.

let writers = [OSLogWriter(subsystem: "com.nike.willow.example", category: "testing")]
let log = Logger(logLevels: [.all], writers: writers)

log.debugMessage("Hello world...coming to your from the os_log APIs!")

Multiple Writers

So what about logging to both a file and the console at the same time? No problem. You can pass multiple LogWriter objects into the Logger initializer. The Logger will execute each LogWriter in the order it was passed in. For example, let's create a FileWriter and combine that with our ConsoleWriter.

public class FileWriter: LogWriter {
    public func writeMessage(_ message: String, logLevel: Logger.LogLevel, modifiers: [LogMessageModifier]?) {
	    var message = message
        modifiers?.map { message = $0.modifyMessage(message, with: logLevel) }
        // Write the formatted message to a file (We'll leave this to you!)
    }

    public func writeMessage(_ message: LogMessage, logLevel: LogLevel) {
        let message = "\(message.name): \(message.attributes)"
        // Write the formatted message to a file (We'll leave this to you!)
    }
}

let writers: [LogMessageWriter] = [FileWriter(), ConsoleWriter()]
let log = Logger(logLevels: [.all], writers: writers)

LogWriter objects can also be selective about which modifiers they want to run for a particular log level. All the examples run all the modifiers, but you can be selective if you want to be.


Advanced Usage

Creating Custom Log Levels

Depending upon the situation, the need to support additional log levels may arise. Willow can easily support additional log levels through the art of bitmasking. Since the internal RawValue of a LogLevel is a UInt, Willow can support up to 32 log levels simultaneously for a single Logger. Since there are 7 default log levels, Willow can support up to 27 custom log levels for a single logger. That should be more than enough to handle even the most complex of logging solutions.

Creating custom log levels is very simple. Here's a quick example of how to do so. First, you must create a LogLevel extension and add your custom values.

extension LogLevel {
    private static var verbose = LogLevel(rawValue: 0b00000000_00000000_00000001_00000000)
}

It's a good idea to make the values for custom log levels var instead of let. In the event of two frameworks using the same custom log level bit mask, the application can re-assign one of the frameworks to a new value.

Now that we have a custom log level called verbose, we need to extend the Logger class to be able to easily call it.

extension Logger {
    public func verboseMessage(_ message: @autoclosure @escaping () -> String) {
    	logMessage(message, with: .verbose)
    }

    public func verboseMessage(_ message: @escaping () -> String) {
    	logMessage(message, with: .verbose)
    }
}

Finally, using the new log level is a simple as...

let log = Logger(logLevels: [.all], writers: [ConsoleWriter()])
log.verboseMessage("My first verbose log message!")

The all log level contains a bitmask where all bits are set to 1. This means that the all log level will contain all custom log levels automatically.

Shared Loggers between Frameworks

Defining a single Logger and sharing that instance several frameworks can be very advantageous, especially with the addition of Frameworks in iOS 8. Now that we're going to be creating more frameworks inside our own apps to be shared between apps, extensions and third party libraries, wouldn't it be nice if we could share Logger instances?

Let's walk through a quick example of a Math framework sharing a Logger with it's parent Calculator app.

//=========== Inside Math.swift ===========
public var log: Logger?

//=========== Calculator.swift ===========
import Math

let writers: [LogMessageWriter] = [FileWriter(), ConsoleWriter()]
var log = Logger(logLevels: [.all], writers: writers)

// Set the Math.log instance to the Calculator.log to share the same Logger instance
Math.log = log

It's very simple to swap out a pre-existing Logger with a new one.

Multiple Loggers, One Queue

The previous example showed how to share Logger instances between multiple frameworks. Something more likely though is that you would want to have each third party library or internal framework to have their own Logger with their own configuration. The one thing that you really want to share is the NSRecursiveLock or DispatchQueue that they run on. This will ensure all your logging is thread-safe. Here's the previous example demonstrating how to create multiple Logger instances and still share the queue.

//=========== Inside Math.swift ===========
public var log: Logger?

//=========== Calculator.swift ===========
import Math

// Create a single queue to share
let sharedQueue = DispatchQueue(label: "com.math.logger", qos: .utility)

// Create the Calculator.log with multiple writers and a .Debug log level
let writers: [LogMessageWriter] = [FileWriter(), ConsoleWriter()]

var log = Logger(
    logLevels: [.all],
    writers: writers,
    executionMethod: .asynchronous(queue: sharedQueue)
)

// Replace the Math.log with a new instance with all the same configuration values except a shared queue
Math.log = Logger(
    logLevels: log.logLevels,
    writers: [ConsoleWriter()],
    executionMethod: .asynchronous(queue: sharedQueue)
)

Willow is a very lightweight library, but its flexibility allows it to become very powerful if you so wish.


Adding Message Filters

Sometimes you may wish to have finer-grained control over when some log messages are included. For instance, if you wanted to ignore logs that have a given attribute, based on whatever dynamic logic you have.

This is useful if you have a way to toggle log subsystems on/off within the app in a DEBUG/ADHOC scenario.

To define a filter, create a type that implements the LogFilter protocol.

Here is an example of a filter that can conditionally exclude noisy logs for an analytics subsystem:

struct AnalyticsLogFilter: LogFilter {
    let name = "analytics"
    
    func shouldInclude(_ message: LogMessage, logLevel: LogLevel) -> Bool {
        // only consider those with a given attribute
        guard message.attributes["subsystem"] == "analytics" else { return true }
        
        return logLevel != .debug
    }
    
    func shouldInclude(_ message: String, logLevel: LogLevel) -> Bool {
        // we don't have any additional context for string messages, so always include
        return true
    }
}

With this filter you can now conditionally add this to the logger:

logger.addFilter(AnalyticsLogFilter())

Or later if you want to remove it:

logger.removeFilter(named: "analytics")
// or 
logger.removeFilters()

Messages that return false from the filter will not be emitted.

Changing log levels at runtime

It can be advantageous in DEBUG and ADHOC builds to allow testers to change the log level to include messages that would otherwise be too noisy to include by default.

You can change the log level at runtime by calling logger.setLogLevels(...). Note that this is passed an OptionSet, so you need to include all of the log levels you want to include.

If you are using the default set of options, you can use the .minimum helper method to include all levels above a given log level. For instance, to include .info and above:

logger.setLogLevels(.minimum(.info))

This method is not supported if you are using custom log levels.

FAQ

Why 5 default log levels? And why are they so named?

Simple...simplicity and elegance. Contextually it gets difficult to understand which log level you need if you have too many. However, that doesn't mean that this is always the perfect solution for everyone or every use case. This is why there are 5 default log levels, with support for easily adding additional ones.

As for the naming, here's our mental breakdown of each log level for an iOS app (obviously it depends on your use case).

  • debug - Highly detailed information of a context
  • info - Summary information of a context
  • event - User driven interactions such as button taps, view transitions, selecting a cell
  • warn - An error occurred but it is recoverable
  • error - A non-recoverable error occurred

When should I use Willow?

If you are starting a new iOS project in Swift and want to take advantage of many new conventions and features of the language, Willow would be a great choice. If you are still working in Objective-C, a pure Objective-C library such as CocoaLumberjack would probably be more appropriate.

Where did the name Willow come from?

Willow is named after the one, the only, Willow tree.


License

Willow is released under the MIT license. See LICENSE for details.

Creators

More Repositories

1

gimme-aws-creds

A CLI that utilizes Okta IdP via SAML to acquire temporary AWS credentials
Python
902
star
2

Elevate

Elevate is a JSON parsing framework that leverages Swift to make parsing simple, reliable and composable.
Swift
612
star
3

koheesio

Python framework for building efficient data pipelines. It promotes modularity and collaboration, enabling the creation of complex pipelines from simple, reusable components.
Python
595
star
4

burnside

Fast and Reliable E2E Web Testing with only Javascript
JavaScript
381
star
5

wingtips

Wingtips is a distributed tracing solution for Java based on the Google Dapper paper.
Java
326
star
6

hal

hal provides an AWS Lambda Custom Runtime environment for your Haskell applications.
Haskell
235
star
7

brickflow

Pythonic Programming Framework to orchestrate jobs in Databricks Workflow
Python
187
star
8

spark-expectations

A Python Library to support running data quality rules while the spark job is runningâš¡
Python
161
star
9

SQift

Powerful Swift wrapper for SQLite
Swift
141
star
10

riposte

Riposte is a Netty-based microservice framework for rapid development of production-ready HTTP APIs.
Java
122
star
11

timeseries-generator

A library to generate synthetic time series data by easy-to-use factors and generator
Python
122
star
12

bartlett

A simple Jenkins command line client to serve your needs.
Haskell
81
star
13

cerberus-doc-site

Secure Property Store for Cloud Applications
CSS
81
star
14

aws-greengrass-core-sdk-rust

Provides an idiomatic Rust wrapper around the AWS Greengrass Core C SDK to more easily enable Greengrass native lambda functions in Rust.
Rust
71
star
15

cerberus

The Cerberus micro-service, a secure property store for cloud applications. It includes a REST API, authentication and encryption features, as well as a self-service web UI for users.
Java
62
star
16

referee

Referee is a UI for using Spinnaker Kayenta as a standalone service.
TypeScript
59
star
17

moirai

Libraries that can be used to determine if a feature should be exposed to a user.
Java
53
star
18

riposte-microservice-template

An example template for quickly creating a new Riposte microservice project.
Java
51
star
19

harbormaster

Harbormaster is a webhook handler for the Kubernetes API.
Go
42
star
20

fastbreak

Fastbreak is a simple Java 8 native circuit breaker supporting async future, blocking, and callback/manual modes.
Java
40
star
21

signal_analog

A troposphere-inspired library for programmatic, declarative definition and management of SignalFx Charts, Dashboards, and Detectors.
Python
39
star
22

backstopper

Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 7 and up.
Java
38
star
23

react-virtualized-item-grid

React component for efficiently rendering a large, scrollable list of items in a series of wrapping rows
JavaScript
38
star
24

knockoff-factory

A library for generating fake data and populating database tables.
Python
34
star
25

pterradactyl

Pterradactyl is a library developed to abstract Terraform configuration from the Terraform environment setup.
Python
32
star
26

lambda-logger-node

A middleware logger that implements the MDC logging pattern for use in AWS NodeJS Lambdas.
TypeScript
29
star
27

lambda-router

JavaScript
23
star
28

bokor

Bokor is a simple, Record and Playback Mock Server written in Node.js, utilized for Service Virtualization.
JavaScript
23
star
29

piggyback

This tool allows you to tunnel SSH (using ProxyCommand) via HTTPS (with Squid Proxy). It is a python implementation of corkscrew, but over https (TLS) instead of http (plaintext).
Python
17
star
30

cerberus-node-client

Node client for interacting with a Cerberus backend. It can be used in Amazon EC2 instances and Amazon Lambdas.
JavaScript
16
star
31

cerberus-java-client

Java Client for Cerberus
Java
14
star
32

cerberus-lifecycle-cli

Command Line Interface for managing a Cerberus environment in AWS
Java
14
star
33

cerberus-python-client

Python Client for Cerberus
Python
13
star
34

cerberus-management-dashboard

A single page react app that is the self service web UI for administration of Safe Deposit Boxes, access control, and data.
HTML
13
star
35

tdd-training-cube

Papercraft cube used as training aid for Outside-In Test Driven Development
11
star
36

cerberus-serverless-components

A collection of AWS Serverless components for Cerberus
Java
11
star
37

cerberus-go-client

A Golang client for interacting with Cerberus, a secure property store for cloud applications.
Go
11
star
38

gradle-localstack

Gradle plugin for working with mock AWS endpoints using LocalStack.
Java
11
star
39

aws-thin-dynamo-node

A small, fast re-implementation of the AWS Dynamo DocumentClient
JavaScript
10
star
40

cerberus-archaius-client

An Archaius property provider implementation backed by Cerberus.
Java
9
star
41

epc-standards

Implementation of decoding GS1 EPC tags
Java
9
star
42

lambda-zipper

Zip up your node lambda code and production dependencies without pruning node_modules
JavaScript
9
star
43

java-vault-client

This is a java based Vault client library for communicating with Vault via HTTP.
Java
8
star
44

cerberus-cli

A CLI for the Cerberus API.
Go
8
star
45

cerberus-integration-tests

Groovy
8
star
46

cerberus-gateway-puppet-module

Puppet Module for installing Nginx and config downloader scripts
Python
8
star
47

Fleam

Scala
7
star
48

cerberus-consul-puppet-module

A Puppet module for installing Hashicorp's Consul as a service with customized start up scripts for Cerberus.
HTML
7
star
49

bluegreen-manager

Java
6
star
50

aws-thin-s3-node

A super-thin AWS S3 client
JavaScript
5
star
51

homebrew-nike

Homebrew formulas provided by Nike, Inc.
Ruby
5
star
52

sagerender

A library for configuring SageMaker pipelines using hierarchical configuration pattern.
Python
5
star
53

dynamo-arc

TypeScript
5
star
54

metrics-new-relic-insights

Reporter to send Dropwizard Metrics to New Relic Insights.
Java
5
star
55

cerberus-vault-puppet-module

A Puppet module for installing Hashicorp's Vault as a service with customized start up scripts for Cerberus.
HTML
5
star
56

cerberus-spring-boot-client

Spring Boot client for interacting with a Cerberus backend.
Java
4
star
57

aws-thin-ses-node

A super-thin AWS Simple Email Service client
JavaScript
3
star
58

dabber

Dabber is a Node CLI tool and AWS Lambda that helps you work with Dynamo.
JavaScript
3
star
59

actions-cerberus-secrets

Read secrets from Cerberus and make it as environment variables in GitHub Actions job so that it can be used in CICD process.
TypeScript
3
star
60

nike-inc.github.io

HTML
3
star
61

phiera

Python
2
star
62

Fawcett

A collection of Monocle lenses for navigating Amazon's API models.
Scala
2
star
63

cerberus-ruby-client

Ruby Client for Cerberus
Ruby
2
star
64

aws-scale

AWS Scaling Made Simple
JavaScript
2
star
65

gimme-a-cli

Gimme a CLI is a Java library for creating quick and easy command line interfaces (CLIs) using JCommander and Spring dependency injection.
Java
2
star
66

gradle-localdynamodb-plugin

XSLT
1
star
67

dynamo-butter

JavaScript
1
star
68

redwiggler

The composting worm. Composts your contract specification and tests and confirms that the contract specification is being followed.
Scala
1
star
69

gimme-a-cli-starter-project

Clone and modify this project to quickly create your own CLI based on the Gimme a CLI library.
Java
1
star