Full Code of dtan4/k8stail for AI

master c3afcb05dee3 cached
19 files
38.1 KB
15.1k tokens
27 symbols
1 requests
Download .txt
Repository: dtan4/k8stail
Branch: master
Commit: c3afcb05dee3
Files: 19
Total size: 38.1 KB

Directory structure:
gitextract_b8pp44tf/

├── .dockerignore
├── .github/
│   └── workflows/
│       ├── release.yaml
│       └── test.yaml
├── .gitignore
├── .goreleaser.yml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── counter.yaml
├── go.mod
├── go.sum
├── logger.go
├── main.go
├── renovate.json
├── tail.go
├── version.go
└── watch.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================

# Created by https://www.gitignore.io/api/go

### Go ###
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# external packages folder
vendor/

bin/
dist/

.git
.gitignore
README.md


================================================
FILE: .github/workflows/release.yaml
================================================
name: Release
on:
  push:
    tags:
      - "v*.*.*"

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: 1.20
      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v6
        with:
          version: latest
          args: release --rm-dist
        env:
          # To upload Homebrew recipe to dtan4/homebrew-tools, we need a personal token
          # instead of Action's temporary token
          GITHUB_TOKEN: ${{ secrets.PERSONAL_GITHUB_TOKEN }}


================================================
FILE: .github/workflows/test.yaml
================================================
name: Test

on:
  push:
    branches:
      - master
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          cache: true
          go-version-file: 'go.mod'

      - name: Run tests
        run: make ci-test

      - name: Send test coverage to Codecov
        uses: codecov/codecov-action@v5

      - name: Run Trivy vulnerability scanner in repo mode
        uses: aquasecurity/trivy-action@v0.36.0
        with:
          scan-type: "fs"
          ignore-unfixed: true
          vuln-type: "os,library"
          severity: "CRITICAL,HIGH"
          exit-code: "1"


================================================
FILE: .gitignore
================================================

# Created by https://www.gitignore.io/api/go

### Go ###
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# external packages folder
vendor/

/bin/
/dist/


================================================
FILE: .goreleaser.yml
================================================
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
before:
  hooks:
    - make clean
    - go mod tidy
builds:
- ldflags:
  - "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
  env:
  - CGO_ENABLED=0
  goos:
  - darwin
  - linux
  - windows
  goarch:
  - 386
  - amd64
  - arm
  - arm64
archives:
- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
  replacements:
    darwin: Darwin
    linux: Linux
    windows: Windows
    386: i386
    amd64: x86_64
  format_overrides:
  - goos: windows
    format: zip
release:
  prerelease: auto
brews:
- tap:
    owner: dtan4
    name: homebrew-tools
  folder: Formula
  homepage: https://github.com/dtan4/k8stail
  description: "`tail -f` experience for Kubernetes Pods"
  skip_upload: auto # skip if the version is rc (e.g. v1.0.0-rc1)
  test: |
    system "#{bin}/k8stail", "-v"
checksum:
  name_template: 'checksums.txt'
snapshot:
  name_template: "{{ .Tag }}-next"
changelog:
  sort: asc
  filters:
    exclude:
    - '^docs:'
    - '^test:'
    - Merge pull request
    - Merge branch


================================================
FILE: CHANGELOG.md
================================================
# [v0.7.0](https://github.com/dtan4/k8stail/releases/tag/v0.7.0) (2022-03-05)

- Go 1.17
- Update dependencies

# [v0.6.0](https://github.com/dtan4/k8stail/releases/tag/v0.6.0) (2018-07-29)

## Features

- Enable external auth provider [#40](https://github.com/dtan4/k8stail/pull/40)

## Others

- Use Go 1.10.3 on Travis CI [#42](https://github.com/dtan4/k8stail/pull/42)
- Upgrade to client-go 8.0.0 [#39](https://github.com/dtan4/k8stail/pull/39)

# [v0.5.2.rc1](https://github.com/dtan4/k8stail/releases/tag/v0.5.2.rc1) (2017-04-27)

## Features

- Add debug flag to enable pprof [#31](https://github.com/dtan4/k8stail/pull/31)

## Fixed

- Close goroutine immediately if there is no valid object [#30](https://github.com/dtan4/k8stail/pull/30)

# [v0.5.1](https://github.com/dtan4/k8stail/releases/tag/v0.5.1) (2017-04-27)

## Fixed

- Print selected context correctly [#28](https://github.com/dtan4/k8stail/pull/28)

# [v0.5.0](https://github.com/dtan4/k8stail/releases/tag/v0.5.0) (2017-04-25)

## Features

- Watch Kubernetes events to detect Pod lifecycle correctly [#26](https://github.com/dtan4/k8stail/pull/26)
- Add more short flags [#25](https://github.com/dtan4/k8stail/pull/25) (thanks @atombender)

# [v0.4.0](https://github.com/dtan4/k8stail/releases/tag/v0.4.0) (2017-04-11)

## Features

- Use default namespace set in kubecfg [#21](https://github.com/dtan4/k8stail/pull/21)
- Add `--no-halt` flag [#18](https://github.com/dtan4/k8stail/pull/18)

## Fixed

- Detect container recreation [#20](https://github.com/dtan4/k8stail/pull/20)

# [v0.3.0](https://github.com/dtan4/k8stail/releases/tag/v0.3.0) (2016-12-12)

## Backward incompatible changes

- Deprecate `-flag` style flag, use `--flag` [#11](https://github.com/dtan4/k8stail/pull/11)

## Features

- Support context switch by `--context` flag [#13](https://github.com/dtan4/k8stail/pull/13) (Thanks @apstndb)

# [v0.2.1](https://github.com/dtan4/k8stail/releases/tag/v0.2.1) (2016-11-16)

Rebuilt binaries to be statically-linked.

# [v0.2.0](https://github.com/dtan4/k8stail/releases/tag/v0.2.0) (2016-11-16)

## Features

- Stream logs of all containers in pod [#5](https://github.com/dtan4/k8stail/pull/5)
- Get kubeconfig path from KUBECONFIG [#4](https://github.com/dtan4/k8stail/pull/4)

# [v0.1.0](https://github.com/dtan4/k8stail/releases/tag/v0.1.0) (2016-11-15)

Initial release.


================================================
FILE: Dockerfile
================================================
FROM golang:1.26 AS builder

WORKDIR /go/src/github.com/dtan4/k8stail

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 go build -o /k8stail

FROM gcr.io/distroless/static:nonroot

COPY --from=builder /k8stail /k8stail

ENTRYPOINT ["/k8stail"]


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2016 Daisuke Fujita

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
NAME    := k8stail
VERSION := $(shell git tag | sort -V -r | head -n1)-next
COMMIT  := $(shell git rev-parse HEAD)
DATE    := $(shell date "+%Y-%m-%dT%H:%M:%S%z")

SRCS    := $(shell find . -name '*.go' -type f)
LDFLAGS := -ldflags="-s -w -X \"main.version=$(VERSION)\" -X \"main.commit=$(COMMIT)\" -X \"main.date=$(DATE)\""

.DEFAULT_GOAL := bin/$(NAME)

export GO111MODULE=on

bin/$(NAME): $(SRCS)
	go build $(LDFLAGS) -o bin/$(NAME)

.PHONY: ci-test
ci-test:
	go test -coverpkg=./... -coverprofile=coverage.txt -v ./...

.PHONY: clean
clean:
	rm -rf bin/*

.PHONY: install
install:
	go install $(LDFLAGS)

.PHONY: test
test:
	go test -cover -v


================================================
FILE: README.md
================================================
# k8stail

[![GitHub Actions](https://github.com/dtan4/k8stail/workflows/Test/badge.svg)](https://github.com/dtan4/k8stail/actions?query=workflow%3ATest+branch%3Amaster)
[![codecov](https://codecov.io/gh/dtan4/k8stail/branch/master/graph/badge.svg)](https://codecov.io/gh/dtan4/k8stail)
[![GitHub release](https://img.shields.io/github/release/dtan4/k8stail.svg)](https://github.com/dtan4/k8stail/releases)

`tail -f` experience for Kubernetes Pods

As you know, `kubectl logs` can stream only ONE pod at the same time. `k8stail` enables you to watch __log streams of ALL pods__ in the specified namespace or labels in real time, like `tail -f`.

![example](_images/example.png)

## Table of Contents

* [Requirements](#requirements)
* [Installation](#installation)
  + [Using Homebrew (OS X only)](#using-homebrew-os-x-only)
  + [Precompiled binary](#precompiled-binary)
  + [From source](#from-source)
  + [Run in a Docker container](#run-in-a-docker-container)
* [Usage](#usage)
  + [kubeconfig file](#kubeconfig-file)
  + [Options](#options)
* [Development](#development)
* [Author](#author)
* [License](#license)

## Requirements

Kubernetes 1.3 or above

## Installation

### Using Homebrew (OS X only)

Formula is available at [dtan4/homebrew-dtan4](https://github.com/dtan4/homebrew-dtan4).

```bash
$ brew tap dtan4/dtan4
$ brew install k8stail
```

### Precompiled binary

Precompiled binaries for Windows, OS X, Linux are available at [Releases](https://github.com/dtan4/k8stail/releases).

### From source

```bash
$ go get -d github.com/dtan4/k8stail
$ cd $GOPATH/src/github.com/dtan4/k8stail
$ make deps
$ make install
```

### Run in a Docker container

Docker image is no longer provided officially.
If you'd like to run k8sec in Docker image, see [`Dockerfile`](Dockerfile) and build image by yourself.

```bash
docker build -t k8stail .
```

## Usage

Logs of all pods, all containers in pod in the specified namespace are streaming. When new pod is added, logs of the pod also appears.
To stop streaming and exit, press `Ctrl-C`.

```bash
$ k8stail --namespace awesome-app
Namespace: awesome-app
Labels:
----------
Pod awesome-app-web-4212725599-67vd4 has detected
Pod awesome-app-web-4212725599-6pduy has detected
Pod awesome-app-web-4212725599-lbuny has detected
Pod awesome-app-web-4212725599-mh3g1 has detected
Pod awesome-app-web-4212725599-pvjsm has detected
[awesome-app-web-4212725599-mh3g1][web]  | creating base compositions...
[awesome-app-web-4212725599-zei9h][web]  |    (47.1ms)  CREATE TABLE "schema_migrations" ("version" character varying NOT NULL)
[awesome-app-web-4212725599-zei9h][web]  |    (45.1ms)  CREATE UNIQUE INDEX  "unique_schema_migrations" ON "schema_migrations"  ("version")
[awesome-app-web-4212725599-zei9h][web]  |   ActiveRecord::SchemaMigration Load (1.8ms)  SELECT "schema_migrations".* FROM "schema_migrations"
[awesome-app-web-4212725599-zei9h][web]  | Migrating to CreatePosts (20160218082522)
```

With `--timestamps` option, log timestamp is printed together.


```bash
$ k8stail --namespace awesome-app --timestamps
Namespace: awesome-app
Labels:
----------
Pod awesome-app-web-4212725599-67vd4 has detected
Pod awesome-app-web-4212725599-6pduy has detected
Pod awesome-app-web-4212725599-lbuny has detected
Pod awesome-app-web-4212725599-mh3g1 has detected
Pod awesome-app-web-4212725599-pvjsm has detected
[awesome-app-web-4212725599-mh3g1][web] 2016-11-15T10:57:22.178667425Z  | creating base compositions...
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.309011520Z  |    (47.1ms)  CREATE TABLE "schema_migrations" ("version" character varying NOT NULL)
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.309053601Z  |    (45.1ms)  CREATE UNIQUE INDEX  "unique_schema_migrations" ON "schema_migrations"  ("version")
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.463700110Z  |   ActiveRecord::SchemaMigration Load (1.8ms)  SELECT "schema_migrations".* FROM "schema_migrations"
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.463743373Z  | Migrating to CreatePosts (20160218082522)
```

With `--labels` option, you can filter pods to watch.

```bash
$ k8stail --namespace awesome-app --labels name=awesome-app-web
Namespace: awesome-app
Labels:    name=awesome-app-web
----------
Pod awesome-app-web-4212725599-67vd4 has detected
Pod awesome-app-web-4212725599-6pduy has detected
Pod awesome-app-web-4212725599-lbuny has detected
Pod awesome-app-web-4212725599-mh3g1 has detected
Pod awesome-app-web-4212725599-pvjsm has detected
[awesome-app-web-4212725599-mh3g1][web]  | creating base compositions...
[awesome-app-web-4212725599-zei9h][web]  |    (47.1ms)  CREATE TABLE "schema_migrations" ("version" character varying NOT NULL)
[awesome-app-web-4212725599-zei9h][web]  |    (45.1ms)  CREATE UNIQUE INDEX  "unique_schema_migrations" ON "schema_migrations"  ("version")
[awesome-app-web-4212725599-zei9h][web]  |   ActiveRecord::SchemaMigration Load (1.8ms)  SELECT "schema_migrations".* FROM "schema_migrations"
[awesome-app-web-4212725599-zei9h][web]  | Migrating to CreatePosts (20160218082522)
```

### kubeconfig file

`k8stail` uses `~/.kube/config` as default. You can specify another path by `KUBECONFIG` environment variable or `--kubeconfig` option. `--kubeconfig` option always overrides `KUBECONFIG` environment variable.

```bash
$ KUBECONFIG=/path/to/kubeconfig k8stail
# or
$ k8stail --kubeconfig=/path/to/kubeconfig
```

### Options

|Option|Description|Required|Default|
|---------|-----------|-------|-------|
|`--context=CONTEXT`|Kubernetes context|||
|`--debug`|Debug mode using pprof (http://localhost:6060)||`false`|
|`--kubeconfig=KUBECONFIG`|Path of kubeconfig||`~/.kube/config`|
|`--labels=LABELS`|Label filter query (e.g. `app=APP,role=ROLE`)|||
|`--namespace=NAMESPACE`|Kubernetes namespace||`default`|
|`--timestamps`|Include timestamps on each line||`false`|
|`-h`, `-help`|Print command line usage|||
|`-v`, `-version`|Print version|||

## Development

Go 1.7 or above is required.

Clone this repository and build using `make`.

```bash
$ go get -d github.com/dtan4/k8stail
$ cd $GOPATH/src/github.com/dtan4/k8stail
$ make
```

## Author

Daisuke Fujita ([@dtan4](https://github.com/dtan4))

## License

[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)


================================================
FILE: counter.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: counter
  labels:
    app: counter
spec:
  replicas: 3
  selector:
    matchLabels:
      app: counter
  template:
    metadata:
      labels:
        app: counter
    spec:
      containers:
      - name: counter-a
        image: busybox
        args: ["/bin/sh", "-c", 'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']
      - name: counter-b
        image: busybox
        args: ["/bin/sh", "-c", 'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']


================================================
FILE: go.mod
================================================
module github.com/dtan4/k8stail

go 1.26.3

require (
	github.com/fatih/color v1.19.0
	github.com/spf13/pflag v1.0.10
	k8s.io/api v0.36.1
	k8s.io/apimachinery v0.36.1
	k8s.io/client-go v0.36.1
)

require (
	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
	github.com/emicklei/go-restful/v3 v3.13.0 // indirect
	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
	github.com/go-logr/logr v1.4.3 // indirect
	github.com/go-openapi/jsonpointer v0.21.0 // indirect
	github.com/go-openapi/jsonreference v0.20.2 // indirect
	github.com/go-openapi/swag v0.23.0 // indirect
	github.com/google/gnostic-models v0.7.0 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/josharian/intern v1.0.0 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/mailru/easyjson v0.7.7 // indirect
	github.com/mattn/go-colorable v0.1.14 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/x448/float16 v0.8.4 // indirect
	go.yaml.in/yaml/v2 v2.4.3 // indirect
	go.yaml.in/yaml/v3 v3.0.4 // indirect
	golang.org/x/net v0.49.0 // indirect
	golang.org/x/oauth2 v0.34.0 // indirect
	golang.org/x/sys v0.42.0 // indirect
	golang.org/x/term v0.39.0 // indirect
	golang.org/x/text v0.33.0 // indirect
	golang.org/x/time v0.14.0 // indirect
	google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
	gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
	gopkg.in/inf.v0 v0.9.1 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
	k8s.io/klog/v2 v2.140.0 // indirect
	k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
	k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
	sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
	sigs.k8s.io/randfill v1.0.0 // indirect
	sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
	sigs.k8s.io/yaml v1.6.0 // indirect
)


================================================
FILE: go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=


================================================
FILE: logger.go
================================================
package main

import (
	"fmt"
	"strings"
	"sync"

	"github.com/fatih/color"
)

var (
	greenBold  = color.New(color.FgGreen, color.Bold)
	yellowBold = color.New(color.FgYellow, color.Bold)
	redBold    = color.New(color.FgRed, color.Bold)
	boldFunc   = color.New(color.Bold).SprintFunc()
	yellowFunc = color.New(color.FgYellow).SprintFunc()
)

// Logger represents logger
type Logger struct {
	m sync.Mutex
}

// NewLogger returns new Logger object
func NewLogger() *Logger {
	return &Logger{
		m: sync.Mutex{},
	}
}

// PrintColorizedLog prints log with the given color
func (l *Logger) PrintColorizedLog(c *color.Color, line string) {
	l.m.Lock()
	defer l.m.Unlock()

	c.Println(line)
}

// PrintHeader prints header
func (l *Logger) PrintHeader(context, namespace, labels string) {
	fmt.Printf("%s %s\n", boldFunc("Context:  "), context)
	fmt.Printf("%s %s\n", boldFunc("Namespace:"), namespace)
	fmt.Printf("%s %s\n", boldFunc("Labels:   "), labels)
	color.New(color.FgYellow).Println("Press Ctrl-C to exit.")
	color.New(color.Bold).Println("----------")
}

// PrintPlainLog prints log with no cosmetics
func (l *Logger) PrintPlainLog(line string) {
	l.m.Lock()
	defer l.m.Unlock()

	fmt.Println(line)
}

// PrintPodDetected prints that Pod was detected
func (l *Logger) PrintPodDetected(pod, container string) {
	l.PrintColorizedLog(greenBold, fmt.Sprintf("Pod:%s Container:%s has been detected", pod, container))
}

// PrintPodDeleted prints that Pod was finished
func (l *Logger) PrintPodFinished(pod, container string) {
	l.PrintColorizedLog(yellowBold, fmt.Sprintf("Pod:%s Container:%s has been finished", pod, container))
}

// PrintPodDeleted prints that Pod was deleted
func (l *Logger) PrintPodDeleted(pod, container string) {
	l.PrintColorizedLog(redBold, fmt.Sprintf("Pod:%s Container:%s has been deleted", pod, container))
}

// PrintPodLog prints Pod log
func (l *Logger) PrintPodLog(pod, container, line string, timestamps bool) {
	l.m.Lock()
	defer l.m.Unlock()

	if timestamps {
		ss := strings.SplitN(line, " ", 2)
		fmt.Printf("[%s][%s] %s  %s %s \n", boldFunc(pod), boldFunc(container), yellowFunc(ss[0]), boldFunc("|"), ss[1])
	} else {
		fmt.Printf("[%s][%s]  %s %s\n", boldFunc(pod), boldFunc(container), boldFunc("|"), line)
	}
}


================================================
FILE: main.go
================================================
package main

import (
	"context"
	"fmt"
	"log"
	"math"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"time"

	flag "github.com/spf13/pflag"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	_ "k8s.io/client-go/plugin/pkg/client/auth"
	"k8s.io/client-go/tools/clientcmd"
)

const (
	debugAddress     = ":6060"
	logSecondsOffset = 10
)

var (
	sinceSeconds = int64(math.Ceil(float64(logSecondsOffset) / float64(time.Second)))
)

func main() {
	var (
		debug       bool
		kubeContext string
		kubeconfig  string
		labels      string
		namespace   string
		timestamps  bool
		version     bool
	)

	flags := flag.NewFlagSet("k8stail", flag.ExitOnError)
	flags.Usage = func() {
		flags.PrintDefaults()
	}

	flags.StringVar(&kubeContext, "context", "", "Kubernetes context")
	flags.BoolVar(&debug, "debug", false, "Debug mode using pprof (http://localhost:6060)")
	flags.StringVar(&kubeconfig, "kubeconfig", "", "Path of kubeconfig")
	flags.StringVarP(&labels, "labels", "l", "", "Label filter query")
	flags.StringVarP(&namespace, "namespace", "n", "", "Kubernetes namespace")
	flags.BoolVarP(&timestamps, "timestamps", "t", false, "Include timestamps on each line")
	flags.BoolVarP(&version, "version", "v", false, "Print version")

	if err := flags.Parse(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if kubeconfig == "" {
		if os.Getenv("KUBECONFIG") != "" {
			kubeconfig = os.Getenv("KUBECONFIG")
		} else {
			kubeconfig = clientcmd.RecommendedHomeFile
		}
	}

	if version {
		printVersion()
		os.Exit(0)
	}

	if debug {
		go func() {
			log.Println(http.ListenAndServe(debugAddress, nil))
		}()
	}

	clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
		&clientcmd.ConfigOverrides{CurrentContext: kubeContext})

	config, err := clientConfig.ClientConfig()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	rawConfig, err := clientConfig.RawConfig()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	var currentContext string

	if kubeContext == "" {
		currentContext = rawConfig.CurrentContext
	} else {
		currentContext = kubeContext
	}

	if namespace == "" {
		if rawConfig.Contexts[currentContext].Namespace == "" {
			namespace = metav1.NamespaceDefault
		} else {
			namespace = rawConfig.Contexts[currentContext].Namespace
		}
	}

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt)

	logger := NewLogger()
	logger.PrintHeader(currentContext, namespace, labels)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	watcher, err := clientset.CoreV1().Pods(namespace).Watch(ctx, metav1.ListOptions{
		LabelSelector: labels,
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	added, finished, deleted := Watch(ctx, watcher)

	tails := NewTailMap()

	go func() {
		for target := range added {
			id := target.GetID()

			if _, ok := tails.Get(id); ok {
				continue
			}

			tail := NewTail(target.Namespace, target.Pod, target.Container, logger, sinceSeconds, timestamps)
			tails.Set(id, tail)
			tail.Start(ctx, clientset)
		}
	}()

	go func() {
		for target := range finished {
			id := target.GetID()

			t, ok := tails.Get(id)
			if !ok {
				continue
			}

			if t.Finished {
				continue
			}

			t.Finish()

			tails.Delete(id)
		}
	}()

	go func() {
		for target := range deleted {
			id := target.GetID()

			t, ok := tails.Get(id)
			if !ok {
				continue
			}

			t.Delete()

			tails.Delete(id)
		}
	}()

	<-sigCh
	cancel()
}


================================================
FILE: renovate.json
================================================
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended",
    "schedule:weekly"
  ],
  "postUpdateOptions": [
    "gomodTidy"
  ],
  "packageRules": [
    {
      "matchDatasources": ["golang-version"],
      "rangeStrategy": "bump"
    },
    {
      "groupName": "all non-major Go dependencies",
      "groupSlug": "all-minor-patch-gomod",
      "matchManagers": [
        "gomod"
      ],
      "matchUpdateTypes": [
        "minor",
        "patch",
        "pin",
        "digest"
      ],
      "automerge": true,
      "automergeType": "branch",
      "matchPackageNames": [
        "!go"
      ]
    },
    {
      "groupName": "all non-major GitHub Actions dependencies",
      "groupSlug": "all-minor-patch-github-actions",
      "matchManagers": [
        "github-actions"
      ],
      "matchUpdateTypes": [
        "minor",
        "patch",
        "pin",
        "digest"
      ],
      "automerge": true,
      "automergeType": "branch"
    },
    {
      "matchUpdateTypes": [
        "minor",
        "patch",
        "pin",
        "digest"
      ],
      "automerge": true,
      "automergeType": "branch"
    }
  ]
}


================================================
FILE: tail.go
================================================
package main

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"sync"

	v1 "k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes"
)

type Tail struct {
	Finished     bool
	closed       chan struct{}
	logger       *Logger
	namespace    string
	pod          string
	container    string
	sinceSeconds int64
	timestamps   bool
}

// NewTail creates new Tail object
func NewTail(namespace, pod, container string, logger *Logger, sinceSeconds int64, timestamps bool) *Tail {
	return &Tail{
		Finished:     false,
		closed:       make(chan struct{}),
		logger:       logger,
		namespace:    namespace,
		pod:          pod,
		container:    container,
		sinceSeconds: sinceSeconds,
		timestamps:   timestamps,
	}
}

// Start starts Pod log streaming
func (t *Tail) Start(ctx context.Context, clientset *kubernetes.Clientset) {
	t.logger.PrintPodDetected(t.pod, t.container)

	go func() {
		rs, err := clientset.CoreV1().Pods(t.namespace).GetLogs(t.pod, &v1.PodLogOptions{
			Container:    t.container,
			Follow:       true,
			SinceSeconds: &t.sinceSeconds,
			Timestamps:   t.timestamps,
		}).Stream(ctx)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			return
		}
		defer rs.Close()

		go func() {
			<-t.closed
			rs.Close()
		}()

		sc := bufio.NewScanner(rs)

		for sc.Scan() {
			t.logger.PrintPodLog(t.pod, t.container, sc.Text(), t.timestamps)
		}
	}()

	go func() {
		<-ctx.Done()
		close(t.closed)
	}()
}

// Finish finishes Pod log streaming with Pod completion
func (t *Tail) Finish() {
	t.logger.PrintPodFinished(t.pod, t.container)
	t.Finished = true
}

// Delete finishes Pod log streaming with Pod deletion
func (t *Tail) Delete() {
	t.logger.PrintPodDeleted(t.pod, t.container)
	close(t.closed)
}

type TailMap struct {
	mu sync.Mutex

	data map[string]*Tail
}

func NewTailMap() *TailMap {
	return &TailMap{
		data: make(map[string]*Tail),
	}
}

func (m *TailMap) Get(k string) (*Tail, bool) {
	m.mu.Lock()
	defer m.mu.Unlock()

	d, ok := m.data[k]

	return d, ok
}

func (m *TailMap) Set(k string, v *Tail) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.data[k] = v
}

func (m *TailMap) Delete(k string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	delete(m.data, k)
}


================================================
FILE: version.go
================================================
package main

import (
	"fmt"
)

var (
	version string
	commit  string
	date    string
)

func printVersion() {
	fmt.Printf("k8stail version: %s, commit: %s, build at: %s\n", version, commit, date)
}


================================================
FILE: watch.go
================================================
package main

import (
	"context"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/watch"
)

type Target struct {
	Namespace string
	Pod       string
	Container string
}

// NewTarget creates new Target object
func NewTarget(namespace, pod, container string) *Target {
	return &Target{
		Namespace: namespace,
		Pod:       pod,
		Container: container,
	}
}

// GetID returns target ID
func (t *Target) GetID() string {
	return t.Namespace + "_" + t.Pod + "_" + t.Container
}

// Watch starts and listens Kubernetes Pod events
func Watch(ctx context.Context, watcher watch.Interface) (chan *Target, chan *Target, chan *Target) {
	added := make(chan *Target)
	finished := make(chan *Target)
	deleted := make(chan *Target)

	go func() {
		for {
			select {
			case e := <-watcher.ResultChan():
				if e.Object == nil {
					return
				}

				pod := e.Object.(*v1.Pod)

				switch e.Type {
				case watch.Added:
					if pod.Status.Phase != v1.PodRunning {
						continue
					}

					for _, container := range pod.Spec.Containers {
						added <- NewTarget(pod.Namespace, pod.Name, container.Name)
					}
				case watch.Modified:
					switch pod.Status.Phase {
					case v1.PodRunning:
						for _, container := range pod.Spec.Containers {
							added <- NewTarget(pod.Namespace, pod.Name, container.Name)
						}
					case v1.PodSucceeded, v1.PodFailed:
						for _, container := range pod.Spec.Containers {
							finished <- NewTarget(pod.Namespace, pod.Name, container.Name)
						}
					}
				case watch.Deleted:
					for _, container := range pod.Spec.Containers {
						deleted <- NewTarget(pod.Namespace, pod.Name, container.Name)
					}
				}

			case <-ctx.Done():
				watcher.Stop()
				return
			}
		}
	}()

	return added, finished, deleted
}
Download .txt
gitextract_b8pp44tf/

├── .dockerignore
├── .github/
│   └── workflows/
│       ├── release.yaml
│       └── test.yaml
├── .gitignore
├── .goreleaser.yml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── counter.yaml
├── go.mod
├── go.sum
├── logger.go
├── main.go
├── renovate.json
├── tail.go
├── version.go
└── watch.go
Download .txt
SYMBOL INDEX (27 symbols across 5 files)

FILE: logger.go
  type Logger (line 20) | type Logger struct
    method PrintColorizedLog (line 32) | func (l *Logger) PrintColorizedLog(c *color.Color, line string) {
    method PrintHeader (line 40) | func (l *Logger) PrintHeader(context, namespace, labels string) {
    method PrintPlainLog (line 49) | func (l *Logger) PrintPlainLog(line string) {
    method PrintPodDetected (line 57) | func (l *Logger) PrintPodDetected(pod, container string) {
    method PrintPodFinished (line 62) | func (l *Logger) PrintPodFinished(pod, container string) {
    method PrintPodDeleted (line 67) | func (l *Logger) PrintPodDeleted(pod, container string) {
    method PrintPodLog (line 72) | func (l *Logger) PrintPodLog(pod, container, line string, timestamps b...
  function NewLogger (line 25) | func NewLogger() *Logger {

FILE: main.go
  constant debugAddress (line 22) | debugAddress     = ":6060"
  constant logSecondsOffset (line 23) | logSecondsOffset = 10
  function main (line 30) | func main() {

FILE: tail.go
  type Tail (line 14) | type Tail struct
    method Start (line 40) | func (t *Tail) Start(ctx context.Context, clientset *kubernetes.Client...
    method Finish (line 75) | func (t *Tail) Finish() {
    method Delete (line 81) | func (t *Tail) Delete() {
  function NewTail (line 26) | func NewTail(namespace, pod, container string, logger *Logger, sinceSeco...
  type TailMap (line 86) | type TailMap struct
    method Get (line 98) | func (m *TailMap) Get(k string) (*Tail, bool) {
    method Set (line 107) | func (m *TailMap) Set(k string, v *Tail) {
    method Delete (line 114) | func (m *TailMap) Delete(k string) {
  function NewTailMap (line 92) | func NewTailMap() *TailMap {

FILE: version.go
  function printVersion (line 13) | func printVersion() {

FILE: watch.go
  type Target (line 10) | type Target struct
    method GetID (line 26) | func (t *Target) GetID() string {
  function NewTarget (line 17) | func NewTarget(namespace, pod, container string) *Target {
  function Watch (line 31) | func Watch(ctx context.Context, watcher watch.Interface) (chan *Target, ...
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (42K chars).
[
  {
    "path": ".dockerignore",
    "chars": 476,
    "preview": "\n# Created by https://www.gitignore.io/api/go\n\n### Go ###\n# Compiled Object files, Static and Dynamic libs (Shared Objec"
  },
  {
    "path": ".github/workflows/release.yaml",
    "chars": 669,
    "preview": "name: Release\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    steps:\n      - "
  },
  {
    "path": ".github/workflows/test.yaml",
    "chars": 762,
    "preview": "name: Test\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps"
  },
  {
    "path": ".gitignore",
    "chars": 451,
    "preview": "\n# Created by https://www.gitignore.io/api/go\n\n### Go ###\n# Compiled Object files, Static and Dynamic libs (Shared Objec"
  },
  {
    "path": ".goreleaser.yml",
    "chars": 1191,
    "preview": "# This is an example goreleaser.yaml file with some sane defaults.\n# Make sure to check the documentation at http://gore"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 2368,
    "preview": "# [v0.7.0](https://github.com/dtan4/k8stail/releases/tag/v0.7.0) (2022-03-05)\n\n- Go 1.17\n- Update dependencies\n\n# [v0.6."
  },
  {
    "path": "Dockerfile",
    "chars": 265,
    "preview": "FROM golang:1.26 AS builder\n\nWORKDIR /go/src/github.com/dtan4/k8stail\n\nCOPY go.mod go.sum ./\nRUN go mod download\n\nCOPY ."
  },
  {
    "path": "LICENSE",
    "chars": 1081,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Daisuke Fujita\n\nPermission is hereby granted, free of charge, to any person ob"
  },
  {
    "path": "Makefile",
    "chars": 647,
    "preview": "NAME    := k8stail\nVERSION := $(shell git tag | sort -V -r | head -n1)-next\nCOMMIT  := $(shell git rev-parse HEAD)\nDATE "
  },
  {
    "path": "README.md",
    "chars": 6335,
    "preview": "# k8stail\n\n[![GitHub Actions](https://github.com/dtan4/k8stail/workflows/Test/badge.svg)](https://github.com/dtan4/k8sta"
  },
  {
    "path": "counter.yaml",
    "chars": 543,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: counter\n  labels:\n    app: counter\nspec:\n  replicas: 3\n  selector"
  },
  {
    "path": "go.mod",
    "chars": 2095,
    "preview": "module github.com/dtan4/k8stail\n\ngo 1.26.3\n\nrequire (\n\tgithub.com/fatih/color v1.19.0\n\tgithub.com/spf13/pflag v1.0.10\n\tk"
  },
  {
    "path": "go.sum",
    "chars": 10859,
    "preview": "github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go"
  },
  {
    "path": "logger.go",
    "chars": 2255,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/fatih/color\"\n)\n\nvar (\n\tgreenBold  = color.New(color.FgGre"
  },
  {
    "path": "main.go",
    "chars": 3702,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\tfla"
  },
  {
    "path": "renovate.json",
    "chars": 1189,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:recommended\",\n    \"schedu"
  },
  {
    "path": "tail.go",
    "chars": 2161,
    "preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/kubernetes\""
  },
  {
    "path": "version.go",
    "chars": 200,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n)\n\nvar (\n\tversion string\n\tcommit  string\n\tdate    string\n)\n\nfunc printVersion() {\n\tfmt.Pri"
  },
  {
    "path": "watch.go",
    "chars": 1752,
    "preview": "package main\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n)\n\ntype Target struct {\n\tNames"
  }
]

About this extraction

This page contains the full source code of the dtan4/k8stail GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (38.1 KB), approximately 15.1k tokens, and a symbol index with 27 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!