Skip to content

Commit 1e80061

Browse files
authored
Merge branch 'dev' into review-shutdown
2 parents 6cf31db + 735f383 commit 1e80061

35 files changed

+639
-130
lines changed

README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,3 +1648,101 @@ PS > (echo $warnings | ConvertTo-Json) | seqcli signal update --json-stdin
16481648
PS > seqcli signal list -i signal-m33302 --json
16491649
{"Title": "Alarms", "Description": "Automatically created", "Filters": [{"De...
16501650
```
1651+
1652+
## Store-and-forward ingestion proxy (preview)
1653+
1654+
The `seqcli forwarder` family of commands provide simple, durable ingestion buffering for occasionally-connected and
1655+
intermittently-disconnected systems. The forwarder implements the Seq ingestion API, so applications that write
1656+
directly to Seq can instead write to the forwarder, which will persist data locally until it can be sent to the
1657+
destination Seq server.
1658+
1659+
> [!NOTE]
1660+
>
1661+
> Forwarder is designed for local use in isolated environments — for example, locally on a firewalled machine, or
1662+
> within a secured container network.
1663+
>
1664+
> The forwarder HTTP API does not authenticate incoming requests. By default, only the ingestion endpoint is exposed,
1665+
> but if you opt into additional APIs such as the ingestion log, ensure the API is not reachable externally. Even in the
1666+
> default configuration, be aware that clients may trigger disk space exhaustion and other issues by sending
1667+
> excessive or maliciously-crafted ingestion traffic.
1668+
1669+
### Running the forwarder
1670+
1671+
To start a forwarder instance at the terminal, listening on port 5341 and forwarding to `seq.example.com`, run:
1672+
1673+
```shell
1674+
seqcli forwarder run --pre --listen http://127.0.0.1:5341 -s https://seq.example.com
1675+
```
1676+
1677+
> While the `forwarder` command group is in preview, all `forwarder` commands require the `--pre` switch; you'll
1678+
> also need to supply `--pre` when requesting help, e.g. `seqcli help forwarder run --pre`.
1679+
1680+
You can test your forwarder using the `seqcli log` command:
1681+
1682+
```shell
1683+
seqcli log -m Test -s http://127.0.0.1:5341
1684+
```
1685+
1686+
If forwarding is successful, the event will appear in the target Seq instance within a few seconds. If it isn't,
1687+
and `seqcli log` didn't report an error, run through these troubleshooting steps:
1688+
1689+
1. Read the output of the `seqcli forwarder run` command: are the paths and URLs in there the ones you expect?
1690+
2. Check the destination Seq server's ingestion log; if the payload is being rejected by Seq, its likely you need to configure an API key for the forwarder's outbound connections, or API key forwarding (see below).
1691+
3. Check the forwarder's log file; this will be in the `SeqCli/Logs` subdirectory of the forwarder's storage path, which will be your user's home directory unless you overrode it with `--storage` or `SEQCLI_STORAGE_PATH`.
1692+
4. Enable the forwarder's ingestion log temporarily, ensuring this isn't accessible to untrusted callers.
1693+
1694+
### The forwarder's ingestion log
1695+
1696+
The ingestion log records ingestion issues in a short in-memory buffer, so repetitive ingestion issues don't chew up
1697+
disk space. The ingestion log may contain fragments of event data so it's not enabled by default; you can turn it on
1698+
with:
1699+
1700+
```shell
1701+
SEQCLI_FORWARDER_DIAGNOSTICS_EXPOSEINGESTIONLOG=True seqcli forwarder run <other args>
1702+
```
1703+
1704+
The log can be retrieved by pointing `seqcli` at the forwarder:
1705+
1706+
```shell
1707+
seqcli diagnostics ingestionlog -s http://127.0.0.1:5341
1708+
```
1709+
1710+
If the ingestion log contains relevant entries but doesn't include enough information to track down the issue, you
1711+
can opt in to showing even more information with `SEQCLI_FORWARDER_DIAGNOSTICS_INGESTIONLOGSHOWDETAIL=True`.
1712+
1713+
### Configuring the forwarder
1714+
1715+
The forwarder reads its configuration from the environment and the `SeqCli.json` file. Run `seqcli config` to view
1716+
all of the supported `forwarder.*` (`SEQCLI_FORWARDER_*`) settings.
1717+
1718+
Locally buffered events are stored in the `SeqCli/Buffer` subdirectory of the directory containing `SeqCli.json`. By
1719+
default, this will be your user account's home folder.
1720+
1721+
To change the location of the `SeqCli.json` config file and local buffers, specify the `--storage` argument
1722+
or set the `SEQCLI_STORAGE_PATH` environment variable when configuring and running the forwarder.
1723+
1724+
### Connection authentication and API key forwarding
1725+
1726+
When connecting to Seq, `seqcli forwarder` will ignore any API keys attached to incoming payloads, and instead use the server URL
1727+
and API key stored in `SeqCli.json`, in the `SEQCLI_CONNECTION_*` environment variables, or passed on
1728+
the `forwarder run` command line.
1729+
1730+
To use the API keys from incoming payloads instead, set `forwarder.useApiKeyForwarding` or `SEQCLI_FORWARDER_USEAPIKEYFORWARDING`
1731+
to `True`.
1732+
1733+
> [!WARNING]
1734+
>
1735+
> In order for durable buffering to work, `seqcli forwarder` needs to store the incoming API keys in its local buffer
1736+
> storage.
1737+
>
1738+
> On Windows, these will be encrypted using machine-scoped DPAPI by default, and on other platforms they'll be saved
1739+
> as plain text. To perform encryption of stored API keys, supply shell scripts in the `encryption.encryptor` and
1740+
> `encryption.decryptor` (`SEQCLI_ENCRYPTION_ENCRYPTOR` and `SEQCLI_ENCRYPTION_DECRYPTOR`) configuration entries. The scripts
1741+
> will need to process data on `STDIN` and `STDOUT`: you can verify their effects by examining the `api.key` files stored
1742+
> in subdirectories of `SeqCli/Buffer`.
1743+
1744+
### Windows service installation
1745+
1746+
On Windows it's possible to install the forwarder as a service, using the `seqcli forwarder install` command. Pass
1747+
a directory to make readable by the service in the `--storage` argument when installing the it. Make sure this directory
1748+
is also passed in `--storage` when configuring the forwarder.

src/SeqCli/Cli/CommandAttribute.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ public class CommandAttribute : Attribute, ICommandMetadata
2323
public string? SubCommand { get; }
2424
public string HelpText { get; }
2525
public string? Example { get; set; }
26-
public bool IsPreview { get; set; }
26+
public FeatureVisibility Visibility { get; set; }
2727

2828
public CommandAttribute(string name, string helpText)
2929
{
3030
Name = name;
3131
HelpText = helpText;
32+
Visibility = FeatureVisibility.Visible;
3233
}
3334

3435
public CommandAttribute(string name, string subCommand, string helpText) : this(name, helpText)

src/SeqCli/Cli/CommandFeature.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
using System;
1615
using System.Collections.Generic;
1716

1817
namespace SeqCli.Cli;
@@ -21,5 +20,5 @@ abstract class CommandFeature
2120
{
2221
public abstract void Enable(OptionSet options);
2322

24-
public virtual IEnumerable<string> GetUsageErrors() => Array.Empty<string>();
23+
public virtual IEnumerable<string> GetUsageErrors() => [];
2524
}

src/SeqCli/Cli/CommandLineHost.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,30 @@ public async Task<int> Run(string[] args, LoggingLevelSwitch levelSwitch)
4141
{
4242
const string prereleaseArg = "--pre", verboseArg = "--verbose";
4343

44-
var norm = args[0].ToLowerInvariant();
45-
var subCommandNorm = args.Length > 1 && !args[1].Contains('-') ? args[1].ToLowerInvariant() : null;
44+
var commandName = args[0].ToLowerInvariant();
45+
var subCommandName = args.Length > 1 && !args[1].Contains('-') ? args[1].ToLowerInvariant() : null;
4646

47-
var pre = args.Any(a => a == prereleaseArg);
47+
var hiddenLegacyCommand = false;
48+
if (subCommandName == null && commandName == "config")
49+
{
50+
hiddenLegacyCommand = true;
51+
subCommandName = "legacy";
52+
}
53+
54+
var featureVisibility = FeatureVisibility.Visible | FeatureVisibility.Hidden;
55+
if (args.Any(a => a.Trim() is prereleaseArg))
56+
featureVisibility |= FeatureVisibility.Preview;
4857

4958
var cmd = _availableCommands.SingleOrDefault(c =>
50-
(!c.Metadata.IsPreview || pre) &&
51-
c.Metadata.Name == norm &&
52-
(c.Metadata.SubCommand == subCommandNorm || c.Metadata.SubCommand == null));
59+
featureVisibility.HasFlag(c.Metadata.Visibility) &&
60+
c.Metadata.Name == commandName &&
61+
(c.Metadata.SubCommand == subCommandName || c.Metadata.SubCommand == null));
5362

5463
if (cmd != null)
5564
{
56-
var amountToSkip = cmd.Metadata.SubCommand == null ? 1 : 2;
57-
var commandSpecificArgs = args.Skip(amountToSkip).Where(arg => cmd.Metadata.Name == "help" || arg != prereleaseArg).ToArray();
58-
65+
var amountToSkip = cmd.Metadata.SubCommand == null || hiddenLegacyCommand ? 1 : 2;
66+
var commandSpecificArgs = args.Skip(amountToSkip).Where(arg => cmd.Metadata.Name == "help" || arg is not prereleaseArg).ToArray();
67+
5968
var verbose = commandSpecificArgs.Any(arg => arg == verboseArg);
6069
if (verbose)
6170
{

src/SeqCli/Cli/CommandMetadata.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ public class CommandMetadata : ICommandMetadata
2020
public string? SubCommand { get; set; }
2121
public required string HelpText { get; set; }
2222
public string? Example { get; set; }
23-
public bool IsPreview { get; set; }
23+
public FeatureVisibility Visibility { get; set; }
2424
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright © Datalust Pty Ltd and Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.Threading.Tasks;
16+
using SeqCli.Cli.Features;
17+
using SeqCli.Config;
18+
19+
// ReSharper disable once UnusedType.Global
20+
21+
namespace SeqCli.Cli.Commands.Config;
22+
23+
[Command("config", "clear", "Clear fields in the `SeqCli.json` file")]
24+
class ClearCommand : Command
25+
{
26+
readonly StoragePathFeature _storagePath;
27+
readonly ConfigKeyFeature _key;
28+
29+
public ClearCommand()
30+
{
31+
_storagePath = Enable<StoragePathFeature>();
32+
_key = Enable<ConfigKeyFeature>();
33+
}
34+
35+
protected override Task<int> Run()
36+
{
37+
var config = SeqCliConfig.ReadFromFile(_storagePath.ConfigFilePath);
38+
39+
KeyValueSettings.Clear(config, _key.GetKey());
40+
SeqCliConfig.WriteToFile(config, _storagePath.ConfigFilePath);
41+
return Task.FromResult(0);
42+
}
43+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright © Datalust Pty Ltd and Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using System.Threading.Tasks;
17+
using SeqCli.Cli.Features;
18+
using SeqCli.Config;
19+
20+
// ReSharper disable once UnusedType.Global
21+
22+
namespace SeqCli.Cli.Commands.Config;
23+
24+
[Command("config", "get", "View a field from the `SeqCli.json` file")]
25+
class GetCommand : Command
26+
{
27+
readonly StoragePathFeature _storagePath;
28+
readonly ConfigKeyFeature _key;
29+
30+
public GetCommand()
31+
{
32+
_storagePath = Enable<StoragePathFeature>();
33+
_key = Enable<ConfigKeyFeature>();
34+
}
35+
36+
protected override Task<int> Run()
37+
{
38+
var config = SeqCliConfig.ReadFromFile(_storagePath.ConfigFilePath);
39+
if (!KeyValueSettings.TryGetValue(config, _key.GetKey(), out var value, out _))
40+
throw new ArgumentException($"Field `{_key.GetKey()}` not found.");
41+
42+
Console.WriteLine(value);
43+
return Task.FromResult(0);
44+
}
45+
}

src/SeqCli/Cli/Commands/ConfigCommand.cs renamed to src/SeqCli/Cli/Commands/Config/LegacyCommand.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@
1919
using SeqCli.Util;
2020
using Serilog;
2121

22-
namespace SeqCli.Cli.Commands;
22+
namespace SeqCli.Cli.Commands.Config;
2323

24-
[Command("config", "View and set fields in `SeqCli.json`; run with no arguments to list all fields")]
25-
class ConfigCommand : Command
24+
[Command("config", "legacy", "View and set fields in `SeqCli.json`; run with no arguments to list all fields", Visibility = FeatureVisibility.Hidden)]
25+
class LegacyCommand : Command
2626
{
2727
string? _key, _value;
2828
bool _clear;
2929
readonly StoragePathFeature _storagePath;
3030

31-
public ConfigCommand()
31+
public LegacyCommand()
3232
{
3333
Options.Add("k|key=", "The field, for example `connection.serverUrl`", k => _key = k);
3434
Options.Add("v|value=", "The field value; if not specified, the command will print the current value", v => _value = v);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright © Datalust Pty Ltd and Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using System.Threading.Tasks;
17+
using SeqCli.Cli.Features;
18+
using SeqCli.Config;
19+
20+
// ReSharper disable once UnusedType.Global
21+
22+
namespace SeqCli.Cli.Commands.Config;
23+
24+
[Command("config", "list", "View all fields in the `SeqCli.json` file")]
25+
class ListCommand : Command
26+
{
27+
readonly StoragePathFeature _storagePath;
28+
29+
public ListCommand()
30+
{
31+
_storagePath = Enable<StoragePathFeature>();
32+
}
33+
34+
protected override Task<int> Run()
35+
{
36+
var config = SeqCliConfig.ReadFromFile(_storagePath.ConfigFilePath);
37+
foreach (var (key, value, _) in KeyValueSettings.Inspect(config))
38+
{
39+
Console.WriteLine($"{key}={value}");
40+
}
41+
return Task.FromResult(0);
42+
}
43+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright © Datalust Pty Ltd and Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using System.IO;
17+
using System.Threading.Tasks;
18+
using SeqCli.Cli.Features;
19+
using SeqCli.Config;
20+
using Serilog;
21+
22+
// ReSharper disable once UnusedType.Global
23+
24+
namespace SeqCli.Cli.Commands.Config;
25+
26+
[Command("config", "set", "Set a field in the `SeqCli.json` file")]
27+
class SetCommand : Command
28+
{
29+
readonly StoragePathFeature _storagePath;
30+
readonly ConfigKeyFeature _key;
31+
readonly ConfigValueFeature _value;
32+
33+
public SetCommand()
34+
{
35+
_storagePath = Enable<StoragePathFeature>();
36+
_key = Enable<ConfigKeyFeature>();
37+
_value = Enable<ConfigValueFeature>();
38+
}
39+
40+
protected override Task<int> Run()
41+
{
42+
var config = SeqCliConfig.ReadFromFile(_storagePath.ConfigFilePath);
43+
44+
KeyValueSettings.Set(config, _key.GetKey(), _value.ReadValue());
45+
SeqCliConfig.WriteToFile(config, _storagePath.ConfigFilePath);
46+
return Task.FromResult(0);
47+
}
48+
}

0 commit comments

Comments
 (0)