Il y a 389 articles et 19 utilisateurs inscrits (ce bandeau est défini dans MediaWiki:Sitenotice)

« Rotation des logs tomcat et Pleade » : différence entre les versions

De AJLSM
Aller à la navigation Aller à la recherche
Page créée avec « # 🧩 Archivage mensuel et nettoyage automatique des logs Tomcat / Java ## 🎯 Objectif Ce document présente un script Bash permettant : - D’**archiver automatique... »
 
 
(12 versions intermédiaires par le même utilisateur non affichées)
Ligne 1 : Ligne 1 :
# 🧩 Archivage mensuel et nettoyage automatique des logs Tomcat / Java
<span id="archivage-mensuel-et-nettoyage-automatique-des-logs-tomcat-java"></span>
= 🏁 Scripts installés dans les VM clients suivants =


## 🎯 Objectif
{| class="wikitable"
|-
! Clients
! Mise en place de la rotation de logs
|-
| ad53
|✅
|-
| Collège de France
|✅
|-
| ad44
|✅
|-
| apbp
|✅
|-
| Pandor
|✅
|-
| ens-lyon
|✅
|-
| cnd
|✅
|-
| ad64
|
|-
| bm-dijon
|
|-
| am-angers
|
|-
| Drac-Alsace
|
|-
| Mercure:/home/ajlsm/recette/...
|✅
|}
 
 
-----
 
= 🧩 Archivage mensuel et nettoyage automatique des logs Tomcat / Java =
 
<span id="objectif"></span>
== 🎯 Objectif ==


Ce document présente un script Bash permettant :
Ce document présente un script Bash permettant :


- D’**archiver automatiquement** les fichiers journaliers et mensuels produits par des applications Java/Tomcat ;
* D’'''archiver automatiquement''' les fichiers journaliers et mensuels produits par des applications Java/Tomcat ;
- De créer **une archive compressée `.gz` unique par mois et par type de log** ;
* De créer '''une archive compressée <code>.gz</code> unique par mois et par type de log''' ;
- De **supprimer les fichiers quotidiens** une fois archivés correctement ;
* De '''supprimer les fichiers quotidiens''' une fois archivés correctement ;
- De **purger les archives** plus anciennes qu’une période définie ;
* De '''purger les archives''' plus anciennes qu’une période définie ;
- De gérer **plusieurs formats de nommage** de fichiers :
* De gérer '''plusieurs formats de nommage''' de fichiers :
  - `manager.2025-11-07.log` (format **point**)
** <code>manager.2025-11-07.log</code> (format '''point''')<br />
  - `sdx-20251107.log` (format **tiret**) 
  - `pleade-stats-202511.log` (format **tiret mensuel**).


---
** <code>sdx-20251107.log</code> (format '''tiret''')<br />
## Paramètres à modifier selon l'installation
 
```
** <code>pleade-stats-202511.log</code> (format '''tiret mensuel''').
#---------- PARAMÈTRES À ADAPTER ----------
 
Ce script automatise la rotation, l’archivage et la purge des logs Tomcat, selon une politique de rétention mensuelle.<br />
Il regroupe les fichiers journaliers (<code>.log</code>, <code>.txt</code>) en '''archives mensuelles compressées (.gz)''', conserve le mois courant en clair, et supprime automatiquement les plus anciens.
 
<span id="paramètres-à-modifier-selon-linstallation"></span>
== Paramètres à modifier selon l’installation ==
 
<pre># ---------- PARAMÈTRES À ADAPTER ----------
DIRS=(
DIRS=(
   "/home/ajlsm/tomcats/preprod/logs"
   &quot;/home/ajlsm/tomcats/preprod/logs&quot;
  "/home/ajlsm/tomcats/slecture/logs"
#  &quot;/home/ajlsm/tomcats/slecture/logs&quot;
   "/home/ajlsm/tomcats/internet-consultation/logs"
   &quot;/home/ajlsm/tomcats/internet/logs&quot;
   "/home/ajlsm/tomcats/internet-consultation/webapps/pleade/WEB-INF/logs"
   &quot;/home/ajlsm/tomcats/internet/webapps/pleade/WEB-INF/logs&quot;
  "/home/ajlsm/tomcats/slecture/webapps/pleade/WEB-INF/logs"
#  &quot;/home/ajlsm/tomcats/slecture/webapps/pleade/WEB-INF/logs&quot;
   "/home/ajlsm/tomcats/preprod/webapps/pleade/WEB-INF/logs"
   &quot;/home/ajlsm/tomcats/preprod/webapps/pleade/WEB-INF/logs&quot;
)
)


RETENTION_MONTHS=6
RETENTION_COMPRESSED_MONTHS=11 # Nombre de mois d’archives compressées à conserver (hors mois courant)
BATCH_SIZE=0                 # 0 = désactivé ; 20 = regrouper dès qu'il y a 20 fichiers
RETENTION_PLAIN_MONTHS=1      # Nombre de mois non compressés (fichiers journaliers ou mensuels actifs)
DRY_RUN=0                   # 1 = ne rien écrire/supprimer, juste logguer
BATCH_SIZE=0                   # 0 = désactivé ; 20 = regrouper dès qu'il y a 20 fichiers
NORMALIZE_SUFFIX=1          # 1 = corriger *.txt-YYYYMMDD / *.log-YYYYMMDD avant archivage
DRY_RUN=0                     # 1 = ne rien écrire/supprimer, juste logguer
ARCHIVE_CURRENT_MONTH=0     # 1 = inclure le mois courant (ré-agrège au fil du mois)
NORMALIZE_SUFFIX=0            # 1 = corriger *.txt-YYYYMMDD / *.log-YYYYMMDD avant archivage
ARCHIVE_CURRENT_MONTH=0       # 1 = inclure le mois courant (ré-agrège au fil du mois)
DEBUG=0                        # 1 = activer affichage détaillé des opérations
# -------------------------------------------


# Familles par style + extensions
# Familles par style + extensions (séparées par espaces si plusieurs)
# DOT  : base.YYYY-MM-DD.ext
declare -A FAMILIES_DOT
declare -A FAMILIES_DOT
FAMILIES_DOT["manager"]="log"
FAMILIES_DOT[&quot;manager&quot;]=&quot;log&quot;
FAMILIES_DOT["localhost_access_log"]="txt"
FAMILIES_DOT[&quot;localhost_access_log&quot;]=&quot;txt&quot;
FAMILIES_DOT["catalina"]="log"
FAMILIES_DOT[&quot;catalina&quot;]=&quot;log&quot;
FAMILIES_DOT["localhost"]="log"
FAMILIES_DOT[&quot;localhost&quot;]=&quot;log&quot;
FAMILIES_DOT["host-manager"]="log"
FAMILIES_DOT[&quot;host-manager&quot;]=&quot;log&quot;


# HYPH : base-YYYYMMDD.ext (quotidien)
declare -A FAMILIES_HYPH
declare -A FAMILIES_HYPH
FAMILIES_HYPH["auto-publication"]="log"
FAMILIES_HYPH[&quot;auto-publication&quot;]=&quot;log&quot;
FAMILIES_HYPH["cocoon"]="log"
FAMILIES_HYPH[&quot;cocoon&quot;]=&quot;log&quot;
FAMILIES_HYPH["oai"]="log"
FAMILIES_HYPH[&quot;oai&quot;]=&quot;log&quot;
FAMILIES_HYPH["publication"]="log"
FAMILIES_HYPH[&quot;publication&quot;]=&quot;log&quot;
FAMILIES_HYPH["sdx"]="log"
FAMILIES_HYPH[&quot;sdx&quot;]=&quot;log&quot;


# HMTH : base-YYYYMM.ext (mensuel)
declare -A FAMILIES_HMTH
declare -A FAMILIES_HMTH
FAMILIES_HMTH["pleade-stats"]="log"
FAMILIES_HMTH[&quot;pleade-stats&quot;]=&quot;log&quot;
```
# -------------------------------------------</pre>
## ⚙️ 1. Script d’archivage (`archive-logs.sh`)
<span id="script-darchivage-archive-logs.sh"></span>
== ⚙️ 1. Script d’archivage (<code>archive-logs.sh</code>) ==


Créer le fichier :
Créer le fichier :<br />
`/usr/local/sbin/archive-logs.sh` 
<code>/usr/local/sbin/archive-logs.sh</code><br />
et lui donner les droits d’exécution (`chmod +x /usr/local/sbin/archive-logs.sh`).
et lui donner les droits d’exécution (<code>chmod +x /usr/local/sbin/archive-logs.sh</code>).


```bash
<syntaxhighlight lang="bash">#!/usr/bin/env bash
#!/usr/bin/env bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export PATH
# archive-logs.sh — regroupe les logs existants en archives mensuelles uniques (.gz)
# archive-logs.sh — regroupe les logs existants en archives mensuelles uniques (.gz)
# - Supporte trois styles de naming :
# - Supporte trois styles de naming :
#  1) DOT  : base.YYYY-MM-DD.ext          (ex: manager.2025-11-07.log)
#  1) DOT  : base.YYYY-MM-DD.ext          (ex: manager.2025-11-07.log)
#  2) HYPH : base-YYYYMMDD.ext            (ex: sdx-20251107.log)
#  2) HYPH : base-YYYYMMDD.ext            (ex: sdx-20251107.log, cocoon-20251107.log)
#  3) HMTH : base-YYYYMM.ext              (ex: pleade-stats-202511.log)
#  3) HMTH : base-YYYYMM.ext              (ex: pleade-stats-202511.log)
# - Ne modifie pas les fichiers lors de la lecture ; supprime uniquement si l’archive est OK
# - Ne modifie pas les fichiers lors de la lecture ; supprime uniquement si l’archive est OK
# - Purge les archives plus vieilles que RETENTION_MONTHS
# - Purge les archives plus vieilles que RETENTION_MONTHS
# - (Optionnel) Normalise *.txt-YYYYMMDD / *.log-YYYYMMDD avant archivage
# - (Optionnel) Normalise *.txt-YYYYMMDD / *.log-YYYYMMDD avant archivage
#
# Installer le script ici : /usr/local/sbin/archive-logs.sh
# Lancer avec crontab -e, par exe :
# Rotation des logs des 3 tomcats tous les 1er de chaque mois.
# 10 6 1 * * /usr/local/sbin/archive-logs.sh >> /home/ajlsm/rotation-logs-pleade/archive-logs-$(date +\%Y-\%m-\%d).log 2>&1


set -euo pipefail
set -euo pipefail
Ligne 76 : Ligne 143 :
   "/home/ajlsm/tomcats/preprod/logs"
   "/home/ajlsm/tomcats/preprod/logs"
   "/home/ajlsm/tomcats/slecture/logs"
   "/home/ajlsm/tomcats/slecture/logs"
   "/home/ajlsm/tomcats/internet-consultation/logs"
   "/home/ajlsm/tomcats/internet/logs"
   "/home/ajlsm/tomcats/internet-consultation/webapps/pleade/WEB-INF/logs"
   "/home/ajlsm/tomcats/internet/webapps/pleade/WEB-INF/logs"
   "/home/ajlsm/tomcats/slecture/webapps/pleade/WEB-INF/logs"
   "/home/ajlsm/tomcats/slecture/webapps/pleade/WEB-INF/logs"
   "/home/ajlsm/tomcats/preprod/webapps/pleade/WEB-INF/logs"
   "/home/ajlsm/tomcats/preprod/webapps/pleade/WEB-INF/logs"
)
)


RETENTION_MONTHS=6
RETENTION_COMPRESSED_MONTHS=11 # Nombre de mois d’archives compressées à conserver (hors mois courant)
BATCH_SIZE=0                 # 0 = désactivé ; 20 = regrouper dès qu'il y a 20 fichiers
RETENTION_PLAIN_MONTHS=1      # Nombre de mois non compressés (fichiers journaliers ou mensuels actifs)
DRY_RUN=0                   # 1 = ne rien écrire/supprimer, juste logguer
BATCH_SIZE=0                   # 0 = désactivé ; 20 = regrouper dès qu'il y a 20 fichiers
NORMALIZE_SUFFIX=1          # 1 = corriger *.txt-YYYYMMDD / *.log-YYYYMMDD avant archivage
DRY_RUN=0                     # 1 = ne rien écrire/supprimer, juste logguer
ARCHIVE_CURRENT_MONTH=0     # 1 = inclure le mois courant (ré-agrège au fil du mois)
NORMALIZE_SUFFIX=0            # 1 = corriger *.txt-YYYYMMDD / *.log-YYYYMMDD avant archivage
ARCHIVE_CURRENT_MONTH=0       # 1 = inclure le mois courant (ré-agrège au fil du mois)
DEBUG=0                        # 1 = activer affichage détaillé des opérations
# -------------------------------------------


# Familles par style + extensions
# Familles par style + extensions (séparées par espaces si plusieurs)
# DOT  : base.YYYY-MM-DD.ext
declare -A FAMILIES_DOT
declare -A FAMILIES_DOT
FAMILIES_DOT["manager"]="log"
FAMILIES_DOT["manager"]="log"
Ligne 96 : Ligne 167 :
FAMILIES_DOT["host-manager"]="log"
FAMILIES_DOT["host-manager"]="log"


# HYPH : base-YYYYMMDD.ext (quotidien)
declare -A FAMILIES_HYPH
declare -A FAMILIES_HYPH
FAMILIES_HYPH["auto-publication"]="log"
FAMILIES_HYPH["auto-publication"]="log"
Ligne 103 : Ligne 175 :
FAMILIES_HYPH["sdx"]="log"
FAMILIES_HYPH["sdx"]="log"


# HMTH : base-YYYYMM.ext (mensuel)
declare -A FAMILIES_HMTH
declare -A FAMILIES_HMTH
FAMILIES_HMTH["pleade-stats"]="log"
FAMILIES_HMTH["pleade-stats"]="log"
Ligne 109 : Ligne 182 :
log() { printf "[%s] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
log() { printf "[%s] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }


# --- Helpers génériques ---
current_month() { date +%Y-%m; }
current_month() { date +%Y-%m; }


month_from_dot_name() { sed -nE 's/.*([0-9]{4})-([0-9]{2})-[0-9]{2}\.[^.]+$/\1-\2/p'; }
# Extrait YYYY-MM depuis DOT (base.YYYY-MM-DD.ext ou base-YYYY-MM-DD.ext)
month_from_hyph_name() { awk 'match($0, /-[0-9]{8}\.[^.]+$/){y=substr($0,RSTART+1,4);m=substr($0,RSTART+5,2);print y"-"m}'; }
month_from_dot_name() {
month_from_hmth_name() { awk 'match($0, /-[0-9]{6}\.[^.]+$/){y=substr($0,RSTART+1,4);m=substr($0,RSTART+5,2);print y"-"m}'; }
  local fname
  if [[ -n "${1:-}" ]]; then
    fname="$1"
  else
    # lit depuis stdin, mais silencieusement sans renvoyer de codes parasites
    IFS= read -r fname || return 1
  fi
  [[ "$fname" =~ [.-]([0-9]{4})-([0-9]{2})-[0-9]{2}\. ]] || return 0
  printf "%s-%s\n" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
}
 
# Extrait YYYY-MM depuis HYPH (base-YYYYMMDD.ext)
month_from_hyph_name() {
  local fname
  if [[ -n "${1:-}" ]]; then
    fname="$1"
  else
    IFS= read -r fname || return 1
  fi
  [[ "$fname" =~ -([0-9]{4})([0-9]{2})[0-9]{2}\. ]] || return 0
  printf "%s-%s\n" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
}


do_mv()     { (( DRY_RUN )) && log "[DRY] mv '$1' '$2'" || mv -- "$1" "$2"; }
# Extrait YYYY-MM depuis HMTH (base-YYYYMM.ext)
do_rm()     { (( DRY_RUN )) && log "[DRY] rm -f $*" || rm -f -- "$@"; }
month_from_hmth_name() {
do_gzip_f() { (( DRY_RUN )) && log "[DRY] gzip -f '$1'" || gzip -f "$1"; }
  local fname
  if [[ -n "${1:-}" ]]; then
    fname="$1"
  else
     IFS= read -r fname || return 1
  fi
  [[ "$fname" =~ -([0-9]{4})([0-9]{2})\. ]] || return 0
  printf "%s-%s\n" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
}


# Actions protégées par DRY_RUN
do_mv()      { (( DRY_RUN )) && log "[DRY] mv -- '$1' '$2'" || mv -- "$1" "$2"; }
do_rm()      { (( DRY_RUN )) && log "[DRY] rm -f -- $*"    || rm -f -- "$@"; }
do_gzip_f()  { (( DRY_RUN )) && log "[DRY] gzip -f '$1'"    || gzip -f "$1";  }
# Normalisation des suffixes parasites: *.txt-YYYYMMDD / *.log-YYYYMMDD
normalize_suffixes() {
normalize_suffixes() {
   for d in "${DIRS[@]}"; do
   for d in "${DIRS[@]}"; do
     [[ -d "$d" ]] || continue
     [[ -d "$d" ]] || continue
     shopt -s nullglob
     shopt -s nullglob
     for f in "$d"/*.txt-????????; do
     for f in "$d"/*.txt-????????; do
       [[ "$f" =~ ^(.*\.txt)-[0-9]{8}$ ]] || continue
       [[ "$f" =~ ^(.*\.txt)-[0-9]{8}$ ]] || continue
Ligne 129 : Ligne 239 :
       do_mv "$f" "$new" && log "$f → $new"
       do_mv "$f" "$new" && log "$f → $new"
     done
     done
     for f in "$d"/*.log-????????; do
     for f in "$d"/*.log-????????; do
       [[ "$f" =~ ^(.*\.log)-[0-9]{8}$ ]] || continue
       [[ "$f" =~ ^(.*\.log)-[0-9]{8}$ ]] || continue
Ligne 135 : Ligne 246 :
       do_mv "$f" "$new" && log "$f → $new"
       do_mv "$f" "$new" && log "$f → $new"
     done
     done
     shopt -u nullglob
     shopt -u nullglob
   done
   done
}
}


# Construit le nom de sortie de l’archive mensuelle
# - style "dot"  -> base.YYYY-MM.ext.gz
# - style "hyph" -> base-YYYY-MM.ext.gz
build_outname() {
build_outname() {
   local style="$1" dir="$2" base="$3" month="$4" ext="$5"
   local style="$1" dir="$2" base="$3" month="$4" ext="$5"
Ligne 148 : Ligne 263 :
}
}


# Archive mensuelle à partir d’une liste de fichiers
make_monthly_archive() {
make_monthly_archive() {
  # args: style dir base ext files...
   local style="$1"; shift
   local style="$1"; shift
   local dir="$1"; shift
   local dir="$1";   shift
   local base="$1"; shift
   local base="$1"; shift
   local ext="$1"; shift
   local ext="$1";   shift
   local -a list=( "$@" )
   local -a list=( "$@" )
   [[ ${#list[@]} -eq 0 ]] && return 0
   [[ ${#list[@]} -eq 0 ]] && return 0


   local month out tmp
   local month="" out tmp # <-- ajout du ="" ici pour éviter "variable sans liaison"
  case "$style" in
# Déterminer le mois sur le 1er fichier selon le style
    dot)  month="$(basename -- "${list[0]}" | month_from_dot_name)";;
case "$style" in
    hyph) month="$(basename -- "${list[0]}" | month_from_hyph_name)";;
  dot)  month="$(month_from_dot_name "$(basename -- "${list[0]}")")";;
    hmth) month="$(basename -- "${list[0]}" | month_from_hmth_name)";;
  hyph) month="$(month_from_hyph_name "$(basename -- "${list[0]}")")";;
  esac
  hmth) month="$(month_from_hmth_name "$(basename -- "${list[0]}")")";;
esac
 
   [[ -z "$month" ]] && { log "⚠️ Impossible d’extraire le mois pour ${list[0]}"; return 0; }
   [[ -z "$month" ]] && { log "⚠️ Impossible d’extraire le mois pour ${list[0]}"; return 0; }


Ligne 167 : Ligne 286 :
   tmp="${out}.tmp"
   tmp="${out}.tmp"


  # Si déjà présent et non vide → on ne refait rien
   if [[ -s "$out" ]]; then
   if [[ -s "$out" ]]; then
     log "Archive déjà présente: $out"
     log "Archive déjà présente: $out"
Ligne 173 : Ligne 293 :


   log "Création archive mensuelle: $out"
   log "Création archive mensuelle: $out"
  # Cas HMTH (un seul fichier par mois en général) : on compresse directement
  if [[ "$style" == "hmth" ]]; then
    # On ne concatène pas, on agrège ce(s) fichier(s) mensuel(s)
    : > "$tmp"
    for f in "${list[@]}"; do
      (( DRY_RUN )) && log "[DRY] cat '$f' >> '$tmp'" || cat -- "$f" >> "$tmp"
    done
    (( DRY_RUN )) && log "[DRY] gzip -f '$tmp' && mv '${tmp}.gz' '$out'" || { do_gzip_f "$tmp"; do_mv "${tmp}.gz" "$out"; }
    if [[ -s "$out" || ( $DRY_RUN -eq 1 ) ]]; then
      log "Archive OK ⇒ suppression des fichiers mensuels (${#list[@]})"
      do_rm "${list[@]}"
    fi
    return 0
  fi
  # Cas DOT/HYPH (quotidiens) : concaténer puis compresser
   : > "$tmp"
   : > "$tmp"
   for f in "${list[@]}"; do
   for f in "${list[@]}"; do
Ligne 180 : Ligne 317 :


   if [[ -s "$out" || ( $DRY_RUN -eq 1 ) ]]; then
   if [[ -s "$out" || ( $DRY_RUN -eq 1 ) ]]; then
     log "Archive OK ⇒ suppression des fichiers (${#list[@]})"
     log "Archive OK ⇒ suppression des quotidiens (${#list[@]})"
     do_rm "${list[@]}"
     do_rm "${list[@]}"
   else
   else
Ligne 187 : Ligne 324 :
}
}


# Parcours d’un style donné
process_style() {
process_style() {
   local style="$1"; shift
   local style="$1" ; shift
   declare -n families="$1"
   declare -n families="$1" # référence associative
   local nowm; nowm="$(current_month)"
   local nowm; nowm="$(current_month)"
  local m y mm


   for d in "${DIRS[@]}"; do
   for d in "${DIRS[@]}"; do
Ligne 200 : Ligne 339 :
         local -a files=()
         local -a files=()
         case "$style" in
         case "$style" in
           dot)  files=( "$d"/${base}.[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9].${ext} );;
           dot)  files=( "$d"/${base}.[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9].${ext} "$d"/${base}-[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9].${ext} );;
           hyph) files=( "$d"/${base}-[0-9][0-9][0-9][0-9][0-1][0-9][0-3][0-9].${ext} );;
           hyph) files=( "$d"/${base}-[0-9][0-9][0-9][0-9][0-1][0-9][0-3][0-9].${ext} );;
           hmth) files=( "$d"/${base}-[0-9][0-9][0-9][0-9][0-1][0-9].${ext} );;
           hmth) files=( "$d"/${base}-[0-9][0-9][0-9][0-9][0-1][0-9].${ext} );;
         esac
         esac
         shopt -u nullglob
         shopt -u nullglob
        (( DEBUG )) && log "[DEBUG] style=$style base=$base ext=$ext dir=$d → ${#files[@]} fichiers"
         [[ ${#files[@]} -eq 0 ]] && continue
         [[ ${#files[@]} -eq 0 ]] && continue


        # Regrouper par mois (hors mois courant sauf si ARCHIVE_CURRENT_MONTH=1)
         declare -A months=()
         declare -A months=()
        local f m
         for f in "${files[@]}"; do
         for f in "${files[@]}"; do
          case "$style" in
  case "$style" in
            dot)  m="$(basename -- "$f" | month_from_dot_name)";;
    dot)  m="$(month_from_dot_name "$(basename -- "$f")")";;
            hyph) m="$(basename -- "$f" | month_from_hyph_name)";;
    hyph) m="$(month_from_hyph_name "$(basename -- "$f")")";;
            hmth) m="$(basename -- "$f" | month_from_hmth_name)";;
    hmth) m="$(month_from_hmth_name "$(basename -- "$f")")";;
          esac
  esac
          [[ -z "$m" ]] && continue
  [[ -z "$m" ]] && continue # <-- ajoute cette ligne
          if (( ! ARCHIVE_CURRENT_MONTH )) && [[ "$m" == "$nowm" ]]; then
  if (( ! ARCHIVE_CURRENT_MONTH )) && [[ "$m" == "$nowm" ]]; then
             continue
    continue
  fi
  months["$m"]=1
done
 
        if (( DEBUG )); then
          if (( ${#months[@]} )); then
            # Affiche les clés (mois) séparées par des espaces
             log "[DEBUG]  Mois détectés : ${!months[@]}"
          else
            log "[DEBUG]  Mois détectés : aucun"
           fi
           fi
          months["$m"]=1
         fi
         done
         [[ ${#months[@]} -eq 0 ]] && continue
         [[ ${#months[@]} -eq 0 ]] && continue


        # Pour chaque mois, lister les fichiers et archiver
         for m in "${!months[@]}"; do
         for m in "${!months[@]}"; do
           local -a month_files=()
           local -a month_files=()
           y="${m%%-*}"; mm="${m##*-}"
           case "$style" in
          while IFS= read -r line; do month_files+=( "$line" ); done < <(
            dot)
            printf "%s\n" "${files[@]}" |
              while IFS= read -r line; do month_files+=( "$line" ); done < <(
            grep -E "/${base}[-\.](${y}${mm}|${m})" |
                printf "%s\n" "${files[@]}" |
            LC_ALL=C sort
                grep -E "/${base}[.-]${m}-[0-9]{2}\.${ext}$" |
           )
                LC_ALL=C sort
              )
              ;;
            hyph)
              y="${m%%-*}"; mm="${m##*-}"
              while IFS= read -r line; do month_files+=( "$line" ); done < <(
                printf "%s\n" "${files[@]}" |
                grep -E "/${base}-${y}${mm}[0-9]{2}\.${ext}$" |
                LC_ALL=C sort
              )
              ;;
            hmth)
              y="${m%%-*}"; mm="${m##*-}"
              while IFS= read -r line; do month_files+=( "$line" ); done < <(
                printf "%s\n" "${files[@]}" |
                grep -E "/${base}-${y}${mm}\.${ext}$" |
                LC_ALL=C sort
              )
              ;;
          esac
 
           (( DEBUG )) && log "[DEBUG]    Archivage mois=$m → ${#month_files[@]} fichiers"
           [[ ${#month_files[@]} -eq 0 ]] && continue
           [[ ${#month_files[@]} -eq 0 ]] && continue
           make_monthly_archive "$style" "$d" "$base" "$ext" "${month_files[@]}"
           make_monthly_archive "$style" "$d" "$base" "$ext" "${month_files[@]}"
         done
         done
        # Mode “paquets de N” (pour DOT & HYPH uniquement)
        if (( BATCH_SIZE > 0 )) && [[ "$style" != "hmth" ]]; then
          local -a oldest=()
          while IFS= read -r line; do oldest+=( "$line" ); done < <(
            printf "%s\n" "${files[@]}" | LC_ALL=C sort | head -n "$BATCH_SIZE"
          )
          if (( ${#oldest[@]} == BATCH_SIZE )); then
            make_monthly_archive "$style" "$d" "$base" "$ext" "${oldest[@]}"
          fi
        fi
       done
       done
     done
     done
Ligne 239 : Ligne 423 :
}
}


# ----- PURGE -----
purge_old_archives() {
purge_old_archives() {
   local days=$(( RETENTION_MONTHS * 31 ))
   local keep_compressed=$((RETENTION_COMPRESSED_MONTHS))
  local keep_plain=$((RETENTION_PLAIN_MONTHS))
  local now_y now_m
  now_y=$(date +%Y)
  now_m=$(date +%m)
 
   for d in "${DIRS[@]}"; do
   for d in "${DIRS[@]}"; do
     [[ -d "$d" ]] || continue
     [[ -d "$d" ]] || continue
     find "$d" -maxdepth 1 -type f \
 
       \( -regex '.*/[^/]+\.[0-9]{4}-[0-9]{2}\.(log|txt)\.gz' \
     # ---- Purge des archives compressées (base-YYYY-MM.ext.gz ou base.YYYY-MM.ext.gz)
        -o -regex '.*/[^/]+-[0-9]{4}-[0-9]{2}\.(log|txt)\.gz' \) \
    while IFS= read -r -d '' f; do
      -mtime +$days -print -delete
      local y m diff
      if [[ "$f" =~ ([0-9]{4})-([0-9]{2})\. ]]; then
        y="${BASH_REMATCH[1]}"
        m="${BASH_REMATCH[2]}"
        diff=$(( (10#$now_y - 10#$y) * 12 + (10#$now_m - 10#$m) ))
        if (( diff > keep_compressed )); then
          (( DRY_RUN )) && log "[DRY] suppression archive ancienne ($diff mois): $f" || { log "Suppression ancienne archive: $f"; rm -f -- "$f"; }
        fi
      fi
    done < <(find "$d" -maxdepth 1 -type f -regextype posix-extended \
       \( -regex '.*/[^/]+\.[0-9]{4}-[0-9]{2}\.(log|txt)\.gz' -o -regex '.*/[^/]+-[0-9]{4}-[0-9]{2}\.(log|txt)\.gz' \) -print0)
 
    # ---- Purge des fichiers mensuels non compressés (cas HMTH: pleade-stats-YYYYMM.log)
    while IFS= read -r -d '' f; do
      local y m diff
      if [[ "$f" =~ ([0-9]{4})([0-9]{2})\.log$ ]]; then
        y="${BASH_REMATCH[1]}"
        m="${BASH_REMATCH[2]}"
        diff=$(( (10#$now_y - 10#$y) * 12 + (10#$now_m - 10#$m) ))
        if (( diff > keep_plain )); then
          (( DRY_RUN )) && log "[DRY] suppression ancien mensuel ($diff mois): $f" || { log "Suppression ancien mensuel: $f"; rm -f -- "$f"; }
        fi
      fi
    done < <(find "$d" -maxdepth 1 -type f -name '*-[0-9][0-9][0-9][0-9][0-1][0-9].log' -print0)
   done
   done
}
}


LOCK="/var/lock/archive-logs.lock"
 
mkdir -p /var/lock
# ----- Verrou -----
exec 9>"$LOCK" || exit 1
LOCK_DIR="/home/ajlsm/.lock"
LOCK="$LOCK_DIR/archive-logs.lock"
mkdir -p "$LOCK_DIR"
exec 9>"$LOCK"
flock -n 9 || { echo "Déjà en cours, abandon."; exit 0; }
flock -n 9 || { echo "Déjà en cours, abandon."; exit 0; }
trap 'flock -u 9' EXIT
trap 'flock -u 9' EXIT
log "=== Début archivage logs ==="
log "=== Début archivage logs ==="
if (( NORMALIZE_SUFFIX )); then
if (( NORMALIZE_SUFFIX )); then
Ligne 266 : Ligne 481 :
purge_old_archives
purge_old_archives
log "=== Fin archivage logs ==="
log "=== Fin archivage logs ==="
```
</syntaxhighlight>
<span id="cron-automatique"></span>


## 🕐 2. Cron automatique
== 🕐 2. Cron automatique ==


### ➤ Exécution mensuelle (chaque 1er du mois à 06:10)
<span id="exécution-mensuelle-chaque-1er-du-mois-à-0610"></span>
```
=== ➤ Exécution mensuelle (chaque 1er du mois à 06:10) ===
10 6 1 * * /usr/local/sbin/archive-logs.sh >> /var/log/archive-logs.log 2>&1
```
### ➤ Option quotidienne (si BATCH_SIZE > 0))
```
5 6 * * * /usr/local/sbin/archive-logs.sh >> /var/log/archive-logs.log 2>&1
```
## 🧹 3. Renommage automatique


Si certains fichiers sont mal nommés (suffixes dupliqués comme `.txt-YYYYMMDD` ou `.log-YYYYMMDD`), le script les **corrige automatiquement** grâce à `NORMALIZE_SUFFIX=1`.\
<pre>10 6 1 * * /usr/local/sbin/archive-logs.sh &gt;&gt; /var/log/archive-logs.log 2&gt;&amp;1</pre>
<span id="option-quotidienne-si-batch_size-0"></span>
=== ➤ Option quotidienne (si BATCH_SIZE &gt; 0)) ===
 
<pre>5 6 * * * /usr/local/sbin/archive-logs.sh &gt;&gt; /var/log/archive-logs.log 2&gt;&amp;1</pre>
<span id="renommage-automatique"></span>
== 🧹 3. Renommage automatique ==
 
Si certains fichiers sont mal nommés (suffixes dupliqués comme <code>.txt-YYYYMMDD</code> ou <code>.log-YYYYMMDD</code>), le script les '''corrige automatiquement''' grâce à <code>NORMALIZE_SUFFIX=1</code>.<br />
Tu peux aussi lancer cette normalisation seule :
Tu peux aussi lancer cette normalisation seule :
```
bash /usr/local/sbin/archive-logs.sh
```
## ✅ 4. Résumé des familles gérées
| Style    | Familles                                                                  | Exemple fichier          | Archive produite              |
| -------- | -------------------------------------------------------------------------- | ------------------------- | ----------------------------- |
| **DOT**  | `manager`, `localhost_access_log`, `catalina`, `localhost`, `host-manager` | `manager.2025-11-07.log`  | `manager.2025-11.log.gz`      |
| **HYPH** | `auto-publication`, `cocoon`, `oai`, `publication`, `sdx`                  | `sdx-20251107.log`        | `sdx-2025-11.log.gz`          |
| **HMTH** | `pleade-stats`                                                            | `pleade-stats-202511.log` | `pleade-stats-2025-11.log.gz` |


## 🔒 5. Sécurité & cohérence
<pre>bash /usr/local/sbin/archive-logs.sh</pre>
<span id="résumé-des-familles-gérées"></span>
== ✅ 4. Résumé des familles gérées ==


* Verrou via `/var/lock/archive-logs.lock` pour éviter les exécutions simultanées.
{| class="wikitable"
|-
! Style
! Familles
! Exemple fichier
! Archive produite
|-
| '''DOT'''
| <code>manager</code>, <code>localhost_access_log</code>, <code>catalina</code>, <code>localhost</code>, <code>host-manager</code>
| <code>manager.2025-11-07.log</code>
| <code>manager.2025-11.log.gz</code>
|-
| '''HYPH'''
| <code>auto-publication</code>, <code>cocoon</code>, <code>oai</code>, <code>publication</code>, <code>sdx</code>
| <code>sdx-20251107.log</code>
| <code>sdx-2025-11.log.gz</code>
|-
| '''HMTH'''
| <code>pleade-stats</code>
| <code>pleade-stats-202511.log</code>
| <code>pleade-stats-2025-11.log.gz</code>
|}


* Test à blanc possible avec `DRY_RUN=1`.
<span id="sécurité-cohérence"></span>
== 🔒 5. Sécurité &amp; cohérence ==


* Purge automatique après `RETENTION_MONTHS` mois.
* Verrou via <code>/var/lock/archive-logs.lock</code> pour éviter les exécutions simultanées.
* Test à blanc possible avec <code>DRY_RUN=1</code>.
* Purge automatique après <code>RETENTION_MONTHS</code> mois.
* Aucune altération des fichiers pendant la lecture : suppression uniquement après création réussie de l’archive.


* Aucune altération des fichiers pendant la lecture : suppression uniquement après création réussie de l’archive.


***
-----
 
<span id="exemple-avantaprès"></span>
== 🧭 Exemple avant/après ==


## 🧭 Exemple avant/après
<span id="avant"></span>
=== Avant ===


### Avant
<pre>sdx-20251101.log
```
sdx-20251101.log
sdx-20251102.log
sdx-20251102.log
sdx-20251103.log
sdx-20251103.log
...
...
pleade-stats-202510.log
pleade-stats-202510.log
</pre>
<span id="après-exécution"></span>
=== Après exécution ===
<pre>sdx-2025-11.log.gz
pleade-stats-2025-10.log.gz</pre>
<span id="configuration-principale"></span>
== ⚙️ Configuration principale ==
<span id="répertoires-surveillés"></span>
=== Répertoires surveillés ===
Le script agit sur les répertoires renseignés dans la section « DIR », par exemple :
<pre>/home/ajlsm/tomcats/preprod/logs
/home/ajlsm/tomcats/internet/logs
/home/ajlsm/tomcats/internet/webapps/pleade/WEB-INF/logs
/home/ajlsm/tomcats/preprod/webapps/pleade/WEB-INF/logs</pre>
<span id="durées-de-conservation"></span>
=== Durées de conservation ===
<syntaxhighlight lang="bash">RETENTION_PLAIN_MONTHS=1        # Mois récents non compressés
RETENTION_COMPRESSED_MONTHS=11  # Mois plus anciens archivés (.gz)</syntaxhighlight>
➡️ En tout : '''1 an de rétention (1 mois clair + 11 mois compressés)'''<br />
➡️ Le mois courant reste toujours présent (non compressé)
<span id="autres-options-importantes"></span>
=== Autres options importantes ===
<syntaxhighlight lang="bash">DRY_RUN=1                  # 1 = simulation (aucune modification)
DEBUG=1                    # 1 = affiche les opérations détaillées
NORMALIZE_SUFFIX=1        # Corrige les fichiers mal nommés (*.log-YYYYMMDD)
ARCHIVE_CURRENT_MONTH=0    # N’archive pas le mois en cours</syntaxhighlight>
-----
<span id="typologie-des-logs"></span>
== 🧩 Typologie des logs ==
{| class="wikitable"
|-
! Style
! Exemple de nom
! Description
! Familles
|-
| '''DOT'''
| <code>manager.2025-11-07.log</code>
| Nom avec point avant la date
| <code>manager</code>, <code>localhost</code>, <code>catalina</code>, <code>host-manager</code>
|-
| '''HYPH'''
| <code>sdx-20251107.log</code>
| Nom avec tiret et date concaténée
| <code>sdx</code>, <code>cocoon</code>, <code>publication</code>, <code>auto-publication</code>, <code>oai</code>
|-
| '''HMTH'''
| <code>pleade-stats-202511.log</code>
| Un seul fichier mensuel
| <code>pleade-stats</code>
|}
Chaque style est traité différemment : - '''DOT / HYPH''' → fusion journalière → archive mensuelle compressée.<br />
- '''HMTH''' → compression directe (un seul fichier par mois).
-----
<span id="étapes-dexécution"></span>
== 🔁 Étapes d’exécution ==
<span id="normalisation"></span>
=== 1. Normalisation ===
Corrige les fichiers mal suffixés :
<pre>catalina.log-20251107 → catalina.log</pre>
<span id="groupement-par-mois"></span>
=== 2. Groupement par mois ===
Détection automatique des fichiers d’un même mois (ex : <code>2025-10</code>).
<span id="archivage-mensuel"></span>
=== 3. Archivage mensuel ===
Concaténation puis compression :
<pre>publication-20251001.log … publication-20251031.log
⮕ publication-2025-10.log.gz</pre>
<span id="purge-automatique"></span>
=== 4. Purge automatique ===
* Supprime les archives <code>.gz</code> plus vieilles que <code>RETENTION_COMPRESSED_MONTHS</code>.
* Supprime les fichiers mensuels <code>.log</code> (HMTH) plus vieux que <code>RETENTION_PLAIN_MONTHS</code>.
* Ignore toujours le mois courant.
<span id="gestion-de-concurrence"></span>
=== 5. Gestion de concurrence ===
Empêche les exécutions simultanées via un verrou :
<pre>/var/lock/archive-logs.lock</pre>
-----
<span id="modes-de-fonctionnement"></span>
== 🧰 Modes de fonctionnement ==
{| class="wikitable"
|-
! Option
! Effet
|-
| <code>DRY_RUN=1</code>
| Mode simulation (aucune écriture/suppression)
|-
| <code>DEBUG=1</code>
| Affiche les mois détectés, les familles traitées et les actions prévues
|-
| <code>ARCHIVE_CURRENT_MONTH=1</code>
| Autorise la réarchivage du mois en cours
|-
| <code>NORMALIZE_SUFFIX=1</code>
| Active la correction des suffixes parasites
|}
-----
<span id="sécurité-et-robustesse"></span>
== 🔒 Sécurité et robustesse ==
* <code>set -euo pipefail</code> : arrêt sur erreur, variable non définie ou pipe brisé.<br />
* <code>flock</code> : verrou d’exécution exclusif.<br />
* <code>umask 022</code> : permissions sûres pour les archives.<br />
* Wrappers <code>do_mv</code>, <code>do_rm</code>, <code>do_gzip_f</code> : toutes les actions respectent le mode <code>DRY_RUN</code>.
-----
<span id="exemple-concret"></span>
== 🧾 Exemple concret ==
Avant archivage :
<pre>publication-20251001.log
publication-20251002.log
...
publication-20251031.log</pre>
Après archivage :
<pre>publication-2025-10.log.gz
publication-20251101.log (mois en cours)</pre>
-----
<span id="cas-particuliers"></span>
== 🧮 Cas particuliers ==
<span id="pleade-stats"></span>
=== 🔹 <code>pleade-stats</code> ===
Fichiers mensuels natifs (HMTH).<br />
Le script les compresse directement, sans concaténation.<br />
Rétention : 12 mois (1 clair + 11 compressés).
<span id="fichiers-journaliers"></span>
=== 🔹 Fichiers journaliers ===
Agrégés mois par mois, puis supprimés une fois l’archive validée.
-----
<span id="exemple-de-nettoyage-automatique"></span>
== 🧹 Exemple de nettoyage automatique ==
<span id="après-un-an-de-fonctionnement"></span>
=== Après un an de fonctionnement : ===
* Le mois courant reste en clair.<br />
* Les 11 derniers mois compressés sont conservés.<br />
* Tout le reste (fichiers + archives anciennes) est supprimé.
-----
<span id="bonnes-pratiques"></span>
== 🔧 Bonnes pratiques ==
<ul>
<li><p>Lancer le script '''une fois par jour''' via cron :</p>
<pre>5 6 * * * /usr/local/sbin/archive-logs.sh &gt;/var/log/archive-logs-run.log 2&gt;&amp;1</pre></li>
<li><p>Tester d’abord en mode <code>DRY_RUN=1</code>.<br />
</p></li>
<li><p>Vérifier les tailles et dates avec :</p>
<pre>ls -lhtr /home/ajlsm/tomcats/*/logs</pre></li></ul>
-----
<span id="résumé-rapide"></span>
== 🏁 Résumé rapide ==
{| class="wikitable"
|-
! Élément
! Valeur
|-
| Rotation
| Mensuelle
|-
| Compression
| GZIP
|-
| Rétention totale
| 12 mois
|-
| Sécurité
| Verrouillage + <code>set -euo pipefail</code>
|-
| Mode test
| <code>DRY_RUN=1</code>
|-
| Journalisation
| Messages datés <code>[YYYY-MM-DD HH:MM:SS]</code>
|}


```
### Après exécution


```
-----
sdx-2025-11.log.gz
pleade-stats-2025-10.log.gz
```
## 📦 Résumé technique


| Élément              | Valeur                            |
'''Dernière version fonctionnelle : novembre 2025'''<br />
| -------------------- | --------------------------------- |
👨‍💻 Mainteneur : Jean-Luc Arvers<br />
| **Fichier script**  | `/usr/local/sbin/archive-logs.sh` |
📍 <code>/usr/local/sbin/archive-logs.sh</code>
| **Logs d’exécution** | `/var/log/archive-logs.log`      |
| **Cron mensuel**    | `10 6 1 * *`                      |
| **Rétention**        | 6 mois (`RETENTION_MONTHS=6`)    |
| **Mode test**        | `DRY_RUN=1`                      |
| **Verrouillage**    | `/var/lock/archive-logs.lock`    |

Dernière version du 15 décembre 2025 à 08:51

🏁 Scripts installés dans les VM clients suivants

[modifier | modifier le wikicode]
Clients Mise en place de la rotation de logs
ad53 ✅
Collège de France ✅
ad44 ✅
apbp ✅
Pandor ✅
ens-lyon ✅
cnd ✅
ad64
bm-dijon
am-angers
Drac-Alsace
Mercure:/home/ajlsm/recette/... ✅



🧩 Archivage mensuel et nettoyage automatique des logs Tomcat / Java

[modifier | modifier le wikicode]

Ce document présente un script Bash permettant :

  • D’archiver automatiquement les fichiers journaliers et mensuels produits par des applications Java/Tomcat ;
  • De créer une archive compressée .gz unique par mois et par type de log ;
  • De supprimer les fichiers quotidiens une fois archivés correctement ;
  • De purger les archives plus anciennes qu’une période définie ;
  • De gérer plusieurs formats de nommage de fichiers :
    • manager.2025-11-07.log (format point)
    • sdx-20251107.log (format tiret)
    • pleade-stats-202511.log (format tiret mensuel).

Ce script automatise la rotation, l’archivage et la purge des logs Tomcat, selon une politique de rétention mensuelle.
Il regroupe les fichiers journaliers (.log, .txt) en archives mensuelles compressées (.gz), conserve le mois courant en clair, et supprime automatiquement les plus anciens.

Paramètres à modifier selon l’installation

[modifier | modifier le wikicode]
# ---------- PARAMÈTRES À ADAPTER ----------
DIRS=(
  "/home/ajlsm/tomcats/preprod/logs"
#  "/home/ajlsm/tomcats/slecture/logs"
  "/home/ajlsm/tomcats/internet/logs"
  "/home/ajlsm/tomcats/internet/webapps/pleade/WEB-INF/logs"
#  "/home/ajlsm/tomcats/slecture/webapps/pleade/WEB-INF/logs"
  "/home/ajlsm/tomcats/preprod/webapps/pleade/WEB-INF/logs"
)

RETENTION_COMPRESSED_MONTHS=11 # Nombre de mois d’archives compressées à conserver (hors mois courant)
RETENTION_PLAIN_MONTHS=1       # Nombre de mois non compressés (fichiers journaliers ou mensuels actifs)
BATCH_SIZE=0                   # 0 = désactivé ; 20 = regrouper dès qu'il y a 20 fichiers
DRY_RUN=0                      # 1 = ne rien écrire/supprimer, juste logguer
NORMALIZE_SUFFIX=0             # 1 = corriger *.txt-YYYYMMDD / *.log-YYYYMMDD avant archivage
ARCHIVE_CURRENT_MONTH=0        # 1 = inclure le mois courant (ré-agrège au fil du mois)
DEBUG=0                        # 1 = activer affichage détaillé des opérations
# -------------------------------------------

# Familles par style + extensions (séparées par espaces si plusieurs)
# DOT  : base.YYYY-MM-DD.ext
declare -A FAMILIES_DOT
FAMILIES_DOT["manager"]="log"
FAMILIES_DOT["localhost_access_log"]="txt"
FAMILIES_DOT["catalina"]="log"
FAMILIES_DOT["localhost"]="log"
FAMILIES_DOT["host-manager"]="log"

# HYPH : base-YYYYMMDD.ext (quotidien)
declare -A FAMILIES_HYPH
FAMILIES_HYPH["auto-publication"]="log"
FAMILIES_HYPH["cocoon"]="log"
FAMILIES_HYPH["oai"]="log"
FAMILIES_HYPH["publication"]="log"
FAMILIES_HYPH["sdx"]="log"

# HMTH : base-YYYYMM.ext (mensuel)
declare -A FAMILIES_HMTH
FAMILIES_HMTH["pleade-stats"]="log"
# -------------------------------------------

⚙️ 1. Script d’archivage (archive-logs.sh)

[modifier | modifier le wikicode]

Créer le fichier :
/usr/local/sbin/archive-logs.sh
et lui donner les droits d’exécution (chmod +x /usr/local/sbin/archive-logs.sh).

#!/usr/bin/env bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export PATH
# archive-logs.sh — regroupe les logs existants en archives mensuelles uniques (.gz)
# - Supporte trois styles de naming :
#   1) DOT  : base.YYYY-MM-DD.ext           (ex: manager.2025-11-07.log)
#   2) HYPH : base-YYYYMMDD.ext             (ex: sdx-20251107.log, cocoon-20251107.log)
#   3) HMTH : base-YYYYMM.ext               (ex: pleade-stats-202511.log)
# - Ne modifie pas les fichiers lors de la lecture ; supprime uniquement si l’archive est OK
# - Purge les archives plus vieilles que RETENTION_MONTHS
# - (Optionnel) Normalise *.txt-YYYYMMDD / *.log-YYYYMMDD avant archivage
# 
# Installer le script ici : /usr/local/sbin/archive-logs.sh
# Lancer avec crontab -e, par exe : 
# Rotation des logs des 3 tomcats tous les 1er de chaque mois.
# 10 6 1 * * /usr/local/sbin/archive-logs.sh >> /home/ajlsm/rotation-logs-pleade/archive-logs-$(date +\%Y-\%m-\%d).log 2>&1

set -euo pipefail
IFS=$'\n\t'
umask 022

# ---------- PARAMÈTRES À ADAPTER ----------
DIRS=(
  "/home/ajlsm/tomcats/preprod/logs"
  "/home/ajlsm/tomcats/slecture/logs"
  "/home/ajlsm/tomcats/internet/logs"
  "/home/ajlsm/tomcats/internet/webapps/pleade/WEB-INF/logs"
  "/home/ajlsm/tomcats/slecture/webapps/pleade/WEB-INF/logs"
  "/home/ajlsm/tomcats/preprod/webapps/pleade/WEB-INF/logs"
)

RETENTION_COMPRESSED_MONTHS=11 # Nombre de mois d’archives compressées à conserver (hors mois courant)
RETENTION_PLAIN_MONTHS=1       # Nombre de mois non compressés (fichiers journaliers ou mensuels actifs)
BATCH_SIZE=0                   # 0 = désactivé ; 20 = regrouper dès qu'il y a 20 fichiers
DRY_RUN=0                      # 1 = ne rien écrire/supprimer, juste logguer
NORMALIZE_SUFFIX=0             # 1 = corriger *.txt-YYYYMMDD / *.log-YYYYMMDD avant archivage
ARCHIVE_CURRENT_MONTH=0        # 1 = inclure le mois courant (ré-agrège au fil du mois)
DEBUG=0                        # 1 = activer affichage détaillé des opérations
# -------------------------------------------

# Familles par style + extensions (séparées par espaces si plusieurs)
# DOT  : base.YYYY-MM-DD.ext
declare -A FAMILIES_DOT
FAMILIES_DOT["manager"]="log"
FAMILIES_DOT["localhost_access_log"]="txt"
FAMILIES_DOT["catalina"]="log"
FAMILIES_DOT["localhost"]="log"
FAMILIES_DOT["host-manager"]="log"

# HYPH : base-YYYYMMDD.ext (quotidien)
declare -A FAMILIES_HYPH
FAMILIES_HYPH["auto-publication"]="log"
FAMILIES_HYPH["cocoon"]="log"
FAMILIES_HYPH["oai"]="log"
FAMILIES_HYPH["publication"]="log"
FAMILIES_HYPH["sdx"]="log"

# HMTH : base-YYYYMM.ext (mensuel)
declare -A FAMILIES_HMTH
FAMILIES_HMTH["pleade-stats"]="log"
# -------------------------------------------

log() { printf "[%s] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }

# --- Helpers génériques ---
current_month() { date +%Y-%m; }

# Extrait YYYY-MM depuis DOT (base.YYYY-MM-DD.ext ou base-YYYY-MM-DD.ext)
month_from_dot_name() {
  local fname
  if [[ -n "${1:-}" ]]; then
    fname="$1"
  else
    # lit depuis stdin, mais silencieusement sans renvoyer de codes parasites
    IFS= read -r fname || return 1
  fi
  [[ "$fname" =~ [.-]([0-9]{4})-([0-9]{2})-[0-9]{2}\. ]] || return 0
  printf "%s-%s\n" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
}

# Extrait YYYY-MM depuis HYPH (base-YYYYMMDD.ext)
month_from_hyph_name() {
  local fname
  if [[ -n "${1:-}" ]]; then
    fname="$1"
  else
    IFS= read -r fname || return 1
  fi
  [[ "$fname" =~ -([0-9]{4})([0-9]{2})[0-9]{2}\. ]] || return 0
  printf "%s-%s\n" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
}

# Extrait YYYY-MM depuis HMTH (base-YYYYMM.ext)
month_from_hmth_name() {
  local fname
  if [[ -n "${1:-}" ]]; then
    fname="$1"
  else
    IFS= read -r fname || return 1
  fi
  [[ "$fname" =~ -([0-9]{4})([0-9]{2})\. ]] || return 0
  printf "%s-%s\n" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
}

# Actions protégées par DRY_RUN
do_mv()      { (( DRY_RUN )) && log "[DRY] mv -- '$1' '$2'" || mv -- "$1" "$2"; }
do_rm()      { (( DRY_RUN )) && log "[DRY] rm -f -- $*"     || rm -f -- "$@"; }
do_gzip_f()  { (( DRY_RUN )) && log "[DRY] gzip -f '$1'"    || gzip -f "$1";  }

# Normalisation des suffixes parasites: *.txt-YYYYMMDD / *.log-YYYYMMDD
normalize_suffixes() {
  for d in "${DIRS[@]}"; do
    [[ -d "$d" ]] || continue
    shopt -s nullglob

    for f in "$d"/*.txt-????????; do
      [[ "$f" =~ ^(.*\.txt)-[0-9]{8}$ ]] || continue
      new="${BASH_REMATCH[1]}"
      [[ -e "$new" ]] && { log "Skip (existe déjà) : $new"; continue; }
      do_mv "$f" "$new" && log "$f → $new"
    done

    for f in "$d"/*.log-????????; do
      [[ "$f" =~ ^(.*\.log)-[0-9]{8}$ ]] || continue
      new="${BASH_REMATCH[1]}"
      [[ -e "$new" ]] && { log "Skip (existe déjà) : $new"; continue; }
      do_mv "$f" "$new" && log "$f → $new"
    done

    shopt -u nullglob
  done
}

# Construit le nom de sortie de l’archive mensuelle
# - style "dot"  -> base.YYYY-MM.ext.gz
# - style "hyph" -> base-YYYY-MM.ext.gz
build_outname() {
  local style="$1" dir="$2" base="$3" month="$4" ext="$5"
  if [[ "$style" == "hyph" ]]; then
    printf "%s/%s-%s.%s.gz" "$dir" "$base" "$month" "$ext"
  else
    printf "%s/%s.%s.%s.gz" "$dir" "$base" "$month" "$ext"
  fi
}

# Archive mensuelle à partir d’une liste de fichiers
make_monthly_archive() {
  # args: style dir base ext files...
  local style="$1"; shift
  local dir="$1";   shift
  local base="$1";  shift
  local ext="$1";   shift
  local -a list=( "$@" )
  [[ ${#list[@]} -eq 0 ]] && return 0

  local month="" out tmp  # <-- ajout du ="" ici pour éviter "variable sans liaison"
# Déterminer le mois sur le 1er fichier selon le style
case "$style" in
  dot)  month="$(month_from_dot_name "$(basename -- "${list[0]}")")";;
  hyph) month="$(month_from_hyph_name "$(basename -- "${list[0]}")")";;
  hmth) month="$(month_from_hmth_name "$(basename -- "${list[0]}")")";;
esac

  [[ -z "$month" ]] && { log "⚠️ Impossible d’extraire le mois pour ${list[0]}"; return 0; }

  out="$(build_outname "$style" "$dir" "$base" "$month" "$ext")"
  tmp="${out}.tmp"

  # Si déjà présent et non vide → on ne refait rien
  if [[ -s "$out" ]]; then
    log "Archive déjà présente: $out"
    return 0
  fi

  log "Création archive mensuelle: $out"

  # Cas HMTH (un seul fichier par mois en général) : on compresse directement
  if [[ "$style" == "hmth" ]]; then
    # On ne concatène pas, on agrège ce(s) fichier(s) mensuel(s)
    : > "$tmp"
    for f in "${list[@]}"; do
      (( DRY_RUN )) && log "[DRY] cat '$f' >> '$tmp'" || cat -- "$f" >> "$tmp"
    done
    (( DRY_RUN )) && log "[DRY] gzip -f '$tmp' && mv '${tmp}.gz' '$out'" || { do_gzip_f "$tmp"; do_mv "${tmp}.gz" "$out"; }
    if [[ -s "$out" || ( $DRY_RUN -eq 1 ) ]]; then
      log "Archive OK ⇒ suppression des fichiers mensuels (${#list[@]})"
      do_rm "${list[@]}"
    fi
    return 0
  fi

  # Cas DOT/HYPH (quotidiens) : concaténer puis compresser
  : > "$tmp"
  for f in "${list[@]}"; do
    (( DRY_RUN )) && log "[DRY] cat '$f' >> '$tmp'" || cat -- "$f" >> "$tmp"
  done
  (( DRY_RUN )) && log "[DRY] gzip -f '$tmp' && mv '${tmp}.gz' '$out'" || { do_gzip_f "$tmp"; do_mv "${tmp}.gz" "$out"; }

  if [[ -s "$out" || ( $DRY_RUN -eq 1 ) ]]; then
    log "Archive OK ⇒ suppression des quotidiens (${#list[@]})"
    do_rm "${list[@]}"
  else
    log "⚠️ Échec création archive: $out (aucune suppression faite)"
  fi
}

# Parcours d’un style donné
process_style() {
  local style="$1" ; shift
  declare -n families="$1"  # référence associative
  local nowm; nowm="$(current_month)"
  local m y mm

  for d in "${DIRS[@]}"; do
    [[ -d "$d" ]] || continue
    for base in "${!families[@]}"; do
      read -r -a exts <<< "${families[$base]}"
      for ext in "${exts[@]}"; do
        shopt -s nullglob
        local -a files=()
        case "$style" in
          dot)  files=( "$d"/${base}.[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9].${ext} "$d"/${base}-[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9].${ext} );;
          hyph) files=( "$d"/${base}-[0-9][0-9][0-9][0-9][0-1][0-9][0-3][0-9].${ext} );;
          hmth) files=( "$d"/${base}-[0-9][0-9][0-9][0-9][0-1][0-9].${ext} );;
        esac
        shopt -u nullglob

        (( DEBUG )) && log "[DEBUG] style=$style base=$base ext=$ext dir=$d → ${#files[@]} fichiers"
        [[ ${#files[@]} -eq 0 ]] && continue

        # Regrouper par mois (hors mois courant sauf si ARCHIVE_CURRENT_MONTH=1)
        declare -A months=()
        for f in "${files[@]}"; do
  case "$style" in
    dot)  m="$(month_from_dot_name "$(basename -- "$f")")";;
    hyph) m="$(month_from_hyph_name "$(basename -- "$f")")";;
    hmth) m="$(month_from_hmth_name "$(basename -- "$f")")";;
  esac
  [[ -z "$m" ]] && continue  # <-- ajoute cette ligne
  if (( ! ARCHIVE_CURRENT_MONTH )) && [[ "$m" == "$nowm" ]]; then
    continue
  fi
  months["$m"]=1
done

         if (( DEBUG )); then
          if (( ${#months[@]} )); then
            # Affiche les clés (mois) séparées par des espaces
            log "[DEBUG]   Mois détectés : ${!months[@]}"
          else
            log "[DEBUG]   Mois détectés : aucun"
          fi
        fi
        [[ ${#months[@]} -eq 0 ]] && continue


        # Pour chaque mois, lister les fichiers et archiver
        for m in "${!months[@]}"; do
          local -a month_files=()
          case "$style" in
            dot)
              while IFS= read -r line; do month_files+=( "$line" ); done < <(
                printf "%s\n" "${files[@]}" |
                grep -E "/${base}[.-]${m}-[0-9]{2}\.${ext}$" |
                LC_ALL=C sort
              )
              ;;
            hyph)
              y="${m%%-*}"; mm="${m##*-}"
              while IFS= read -r line; do month_files+=( "$line" ); done < <(
                printf "%s\n" "${files[@]}" |
                grep -E "/${base}-${y}${mm}[0-9]{2}\.${ext}$" |
                LC_ALL=C sort
              )
              ;;
            hmth)
              y="${m%%-*}"; mm="${m##*-}"
              while IFS= read -r line; do month_files+=( "$line" ); done < <(
                printf "%s\n" "${files[@]}" |
                grep -E "/${base}-${y}${mm}\.${ext}$" |
                LC_ALL=C sort
              )
              ;;
          esac

          (( DEBUG )) && log "[DEBUG]     Archivage mois=$m → ${#month_files[@]} fichiers"
          [[ ${#month_files[@]} -eq 0 ]] && continue
          make_monthly_archive "$style" "$d" "$base" "$ext" "${month_files[@]}"
        done

        # Mode “paquets de N” (pour DOT & HYPH uniquement)
        if (( BATCH_SIZE > 0 )) && [[ "$style" != "hmth" ]]; then
          local -a oldest=()
          while IFS= read -r line; do oldest+=( "$line" ); done < <(
            printf "%s\n" "${files[@]}" | LC_ALL=C sort | head -n "$BATCH_SIZE"
          )
          if (( ${#oldest[@]} == BATCH_SIZE )); then
            make_monthly_archive "$style" "$d" "$base" "$ext" "${oldest[@]}"
          fi
        fi
      done
    done
  done
}

# ----- PURGE -----
purge_old_archives() {
  local keep_compressed=$((RETENTION_COMPRESSED_MONTHS))
  local keep_plain=$((RETENTION_PLAIN_MONTHS))
  local now_y now_m
  now_y=$(date +%Y)
  now_m=$(date +%m)

  for d in "${DIRS[@]}"; do
    [[ -d "$d" ]] || continue

    # ---- Purge des archives compressées (base-YYYY-MM.ext.gz ou base.YYYY-MM.ext.gz)
    while IFS= read -r -d '' f; do
      local y m diff
      if [[ "$f" =~ ([0-9]{4})-([0-9]{2})\. ]]; then
        y="${BASH_REMATCH[1]}"
        m="${BASH_REMATCH[2]}"
        diff=$(( (10#$now_y - 10#$y) * 12 + (10#$now_m - 10#$m) ))
        if (( diff > keep_compressed )); then
          (( DRY_RUN )) && log "[DRY] suppression archive ancienne ($diff mois): $f" || { log "Suppression ancienne archive: $f"; rm -f -- "$f"; }
        fi
      fi
    done < <(find "$d" -maxdepth 1 -type f -regextype posix-extended \
      \( -regex '.*/[^/]+\.[0-9]{4}-[0-9]{2}\.(log|txt)\.gz' -o -regex '.*/[^/]+-[0-9]{4}-[0-9]{2}\.(log|txt)\.gz' \) -print0)

    # ---- Purge des fichiers mensuels non compressés (cas HMTH: pleade-stats-YYYYMM.log)
    while IFS= read -r -d '' f; do
      local y m diff
      if [[ "$f" =~ ([0-9]{4})([0-9]{2})\.log$ ]]; then
        y="${BASH_REMATCH[1]}"
        m="${BASH_REMATCH[2]}"
        diff=$(( (10#$now_y - 10#$y) * 12 + (10#$now_m - 10#$m) ))
        if (( diff > keep_plain )); then
          (( DRY_RUN )) && log "[DRY] suppression ancien mensuel ($diff mois): $f" || { log "Suppression ancien mensuel: $f"; rm -f -- "$f"; }
        fi
      fi
    done < <(find "$d" -maxdepth 1 -type f -name '*-[0-9][0-9][0-9][0-9][0-1][0-9].log' -print0)
  done
}


# ----- Verrou -----
LOCK_DIR="/home/ajlsm/.lock"
LOCK="$LOCK_DIR/archive-logs.lock"
mkdir -p "$LOCK_DIR"
exec 9>"$LOCK"
flock -n 9 || { echo "Déjà en cours, abandon."; exit 0; }
trap 'flock -u 9' EXIT
log "=== Début archivage logs ==="
if (( NORMALIZE_SUFFIX )); then
  log "Normalisation des suffixes *.txt-YYYYMMDD / *.log-YYYYMMDD"
  normalize_suffixes
fi
process_style dot  FAMILIES_DOT
process_style hyph FAMILIES_HYPH
process_style hmth FAMILIES_HMTH
purge_old_archives
log "=== Fin archivage logs ==="

🕐 2. Cron automatique

[modifier | modifier le wikicode]

➤ Exécution mensuelle (chaque 1er du mois à 06:10)

[modifier | modifier le wikicode]
10 6 1 * * /usr/local/sbin/archive-logs.sh >> /var/log/archive-logs.log 2>&1

➤ Option quotidienne (si BATCH_SIZE > 0))

[modifier | modifier le wikicode]
5 6 * * * /usr/local/sbin/archive-logs.sh >> /var/log/archive-logs.log 2>&1

🧹 3. Renommage automatique

[modifier | modifier le wikicode]

Si certains fichiers sont mal nommés (suffixes dupliqués comme .txt-YYYYMMDD ou .log-YYYYMMDD), le script les corrige automatiquement grâce à NORMALIZE_SUFFIX=1.
Tu peux aussi lancer cette normalisation seule :

bash /usr/local/sbin/archive-logs.sh

✅ 4. Résumé des familles gérées

[modifier | modifier le wikicode]
Style Familles Exemple fichier Archive produite
DOT manager, localhost_access_log, catalina, localhost, host-manager manager.2025-11-07.log manager.2025-11.log.gz
HYPH auto-publication, cocoon, oai, publication, sdx sdx-20251107.log sdx-2025-11.log.gz
HMTH pleade-stats pleade-stats-202511.log pleade-stats-2025-11.log.gz

🔒 5. Sécurité & cohérence

[modifier | modifier le wikicode]
  • Verrou via /var/lock/archive-logs.lock pour éviter les exécutions simultanées.
  • Test à blanc possible avec DRY_RUN=1.
  • Purge automatique après RETENTION_MONTHS mois.
  • Aucune altération des fichiers pendant la lecture : suppression uniquement après création réussie de l’archive.



🧭 Exemple avant/après

[modifier | modifier le wikicode]

sdx-20251101.log
sdx-20251102.log
sdx-20251103.log
...
pleade-stats-202510.log

Après exécution

[modifier | modifier le wikicode]
sdx-2025-11.log.gz
pleade-stats-2025-10.log.gz

⚙️ Configuration principale

[modifier | modifier le wikicode]

Répertoires surveillés

[modifier | modifier le wikicode]

Le script agit sur les répertoires renseignés dans la section « DIR », par exemple :

/home/ajlsm/tomcats/preprod/logs
/home/ajlsm/tomcats/internet/logs
/home/ajlsm/tomcats/internet/webapps/pleade/WEB-INF/logs
/home/ajlsm/tomcats/preprod/webapps/pleade/WEB-INF/logs

Durées de conservation

[modifier | modifier le wikicode]
RETENTION_PLAIN_MONTHS=1        # Mois récents non compressés
RETENTION_COMPRESSED_MONTHS=11  # Mois plus anciens archivés (.gz)

➡️ En tout : 1 an de rétention (1 mois clair + 11 mois compressés)
➡️ Le mois courant reste toujours présent (non compressé)

Autres options importantes

[modifier | modifier le wikicode]
DRY_RUN=1                  # 1 = simulation (aucune modification)
DEBUG=1                    # 1 = affiche les opérations détaillées
NORMALIZE_SUFFIX=1         # Corrige les fichiers mal nommés (*.log-YYYYMMDD)
ARCHIVE_CURRENT_MONTH=0    # N’archive pas le mois en cours

🧩 Typologie des logs

[modifier | modifier le wikicode]
Style Exemple de nom Description Familles
DOT manager.2025-11-07.log Nom avec point avant la date manager, localhost, catalina, host-manager
HYPH sdx-20251107.log Nom avec tiret et date concaténée sdx, cocoon, publication, auto-publication, oai
HMTH pleade-stats-202511.log Un seul fichier mensuel pleade-stats

Chaque style est traité différemment : - DOT / HYPH → fusion journalière → archive mensuelle compressée.
- HMTH → compression directe (un seul fichier par mois).



🔁 Étapes d’exécution

[modifier | modifier le wikicode]

1. Normalisation

[modifier | modifier le wikicode]

Corrige les fichiers mal suffixés :

catalina.log-20251107 → catalina.log

2. Groupement par mois

[modifier | modifier le wikicode]

Détection automatique des fichiers d’un même mois (ex : 2025-10).

3. Archivage mensuel

[modifier | modifier le wikicode]

Concaténation puis compression :

publication-20251001.log … publication-20251031.log
⮕ publication-2025-10.log.gz

4. Purge automatique

[modifier | modifier le wikicode]
  • Supprime les archives .gz plus vieilles que RETENTION_COMPRESSED_MONTHS.
  • Supprime les fichiers mensuels .log (HMTH) plus vieux que RETENTION_PLAIN_MONTHS.
  • Ignore toujours le mois courant.

5. Gestion de concurrence

[modifier | modifier le wikicode]

Empêche les exécutions simultanées via un verrou :

/var/lock/archive-logs.lock

🧰 Modes de fonctionnement

[modifier | modifier le wikicode]
Option Effet
DRY_RUN=1 Mode simulation (aucune écriture/suppression)
DEBUG=1 Affiche les mois détectés, les familles traitées et les actions prévues
ARCHIVE_CURRENT_MONTH=1 Autorise la réarchivage du mois en cours
NORMALIZE_SUFFIX=1 Active la correction des suffixes parasites



🔒 Sécurité et robustesse

[modifier | modifier le wikicode]
  • set -euo pipefail : arrêt sur erreur, variable non définie ou pipe brisé.
  • flock : verrou d’exécution exclusif.
  • umask 022 : permissions sûres pour les archives.
  • Wrappers do_mv, do_rm, do_gzip_f : toutes les actions respectent le mode DRY_RUN.



🧾 Exemple concret

[modifier | modifier le wikicode]

Avant archivage :

publication-20251001.log
publication-20251002.log
...
publication-20251031.log

Après archivage :

publication-2025-10.log.gz
publication-20251101.log (mois en cours)

🧮 Cas particuliers

[modifier | modifier le wikicode]

🔹 pleade-stats

[modifier | modifier le wikicode]

Fichiers mensuels natifs (HMTH).
Le script les compresse directement, sans concaténation.
Rétention : 12 mois (1 clair + 11 compressés).

🔹 Fichiers journaliers

[modifier | modifier le wikicode]

Agrégés mois par mois, puis supprimés une fois l’archive validée.



🧹 Exemple de nettoyage automatique

[modifier | modifier le wikicode]

Après un an de fonctionnement :

[modifier | modifier le wikicode]
  • Le mois courant reste en clair.
  • Les 11 derniers mois compressés sont conservés.
  • Tout le reste (fichiers + archives anciennes) est supprimé.



🔧 Bonnes pratiques

[modifier | modifier le wikicode]
  • Lancer le script une fois par jour via cron :

    5 6 * * * /usr/local/sbin/archive-logs.sh >/var/log/archive-logs-run.log 2>&1
  • Tester d’abord en mode DRY_RUN=1.

  • Vérifier les tailles et dates avec :

    ls -lhtr /home/ajlsm/tomcats/*/logs



🏁 Résumé rapide

[modifier | modifier le wikicode]
Élément Valeur
Rotation Mensuelle
Compression GZIP
Rétention totale 12 mois
Sécurité Verrouillage + set -euo pipefail
Mode test DRY_RUN=1
Journalisation Messages datés [YYYY-MM-DD HH:MM:SS]



Dernière version fonctionnelle : novembre 2025
👨‍💻 Mainteneur : Jean-Luc Arvers
📍 /usr/local/sbin/archive-logs.sh