Дисклеймер.

Статья написана исключительно в развлекательных целях.

Как все начиналось

Любой администратор любой информационной системе знает, что мест для автоматизации в его работе бесконечное множество. Какая огромная ниша для автоматизации прячется в задачах обработки логов, сбора статистики, реакции на события систем.

Вот однажды и моего коллегу утомило обрабатывать гигабайты логов exchange сервера, чтобы найти нужные данные. Вполне здравая мысль посетила его светлый ум – «Куда как проще работать с данными если они лежат в какой ни будь БД». И не мудрствуя лукаво он лихо наваял скрипт на том что было установлено на его свеженьком Linux. Так появился первый скрипт на python который занимался складированием логов в MongoDB.

Через какое-то время в разговоре со мной он вскользь упомянул про этот механизм и изъявил желание переписать его на JS. Мне мысль понравилась, и я предложил написать этот скрипт на всем что есть под рукой. Чем мы и занялись в обеденные перерывы.

Входные данные

Для тестирования мы выбрали 545 json файла общим размером 1Гб. Количество документов в них примерно 687 тысяч.

В качестве сервера БД была выбрана MongoDB.

Дополнительные требования:

  • Так как экспортер логов exchange-а хранит дату в каком то совершенно непотребном виде (“Timestamp“:  “\/Date(1649404808029)\/“), необходимо было привести эту запись к тому который понимает Mongo.

  • Если поле “MessageLatencyType” имеет значение отличное от 0, то заменить значение поля MessageLatency на MessageLatency. TotalMilliseconds.

Вот пример этих полей:

"MessageLatencyType":  1

"MessageLatency":  {
                               "Days":  0,
                               "Hours":  0,
                               "Milliseconds":  325,
                               "Minutes":  0,
                               "Seconds":  0,
                               "Ticks":  3250000,
                               "TotalDays":  3.7615740740740738E-06,
                               "TotalHours":  9.0277777777777774E-05,
                               "TotalMilliseconds":  325,
                               "TotalMinutes":  0.0054166666666666669,
                               "TotalSeconds":  0.325,
                               "Sign":  1
                           }
  • Стараться использовать только нативные или официальные инструменты для работы с JSON и Mongo.

  • Без читерства )))

Творческая часть

Что же оказалось у нас под рукой?

Python

В сущности, питон прекрасно подходит как для повседневных административных задач, так и для конкретно этой. В качестве драйвер использовался pymongo.

Hidden text
def get_database():
  from pymongo import MongoClient
  import pymongo
  CONNECTION_STRING = 'mongodb://localhost:27017'
  client = MongoClient(CONNECTION_STRING)
  return client['sample_database_python']


def convert_datetime(unix_timestamp):
  import datetime
  milliseconds = 0
  if len(unix_timestamp) == 13:
      milliseconds = int(unix_timestamp[-3:])
      unix_timestamp = float(unix_timestamp[0:-3])
  the_date = datetime.datetime.utcfromtimestamp(unix_timestamp)
  the_date += datetime.timedelta(milliseconds=milliseconds)
  return the_date


def import_file(file_name):
  import json
  with open(file_name, 'r', encoding='utf8') as  sample_file:
    sample_data = json.load(sample_file)
    for line in sample_data:
      line['Timestamp'] = convert_datetime(line['Timestamp'][6:-2])
      if (line['MessageLatencyType'] != 0):
        line['MessageLatency'] = line['MessageLatency']['Milliseconds']
    sample_collection.insert_many(sample_data)


if __name__ == '__main__':    
    database = get_database()

sample_collection = database['sample_collection']

import os
path = './data/'
for file in os.listdir(path):
  if file.endswith('.json'):
    import_file(f"{path}{file}")
    # print(f"Imported file: {path}{file}")

JS

Прекрасный и очень шустрый язык. Устанавливаем драйвер БД и вперед.

Hidden text
const { MongoClient } = require("mongodb");
const client = new MongoClient("mongodb://127.0.0.1:27017");

const fs = require("fs");
// const path = "./data/";
const path = "/media/user/SOFT/SHARED/MX/";

async function run() {
  try {
    await client.connect();
    const database = client.db("exchange_logs");
    const logsCollection = database.collection("records");

    const files = fs.readdirSync(path);
    for (const file of files) {
      await importFile(`${path}${file}`, logsCollection, database);
      console.log(`Imported file: ${path}${file}`);
    }
  } catch (err) {
    console.log(err);
  } finally {
    await client.close();
  }
}

async function importFile(fileName, logsCollection, database) {
  try {
    const sample_data = JSON.parse(fs.readFileSync(fileName, "utf8"));
    sample_data.forEach((record) => {
      record.Timestamp = new Date(+record.Timestamp.slice(6, 19));
      if (record.MessageLatencyType !== 0) record.MessageLatency = record.MessageLatency.Milliseconds;
    });
    const results = await logsCollection.insertMany(sample_data);
    // console.log(results);
    await database.collection("logs").insertOne({ FileName: fileName, Timestamp: new Date(), Success: true, InsertedCount: results.insertedCount });
  } catch (error) {
    await database.collection("logs").insertOne({ FileName: fileName, Timestamp: new Date(), Success: false });
  }
}

run().catch(console.dir);

PowerShell 7,5

5:

К сожалению официальных драйверов под PS я не нашел, поэтому пришлось брать то что есть: Install-Module Mdbc.

Данный проект не новый, но он до сих пор не имеет реализации, столь нужного, командлета как InsertMany. Поэтому придется обходится «построчной» записью. Это конечно намного медленнее, но для наших нужд вполне подойдет. Для работы с JSON будем применять JavaScriptSerializer из стандартной библиотеки System.Web.Script.Serialization.

Hidden text
Import-Module Mdbc
Add-Type -AssemblyName System.Web.Extensions

[string]$MongoUrl = "mongodb://localhost:27017"
[string]$Db = "sample_database_ps"
[string]$CollectionName = "sample_collection_5"

Connect-Mdbc $MongoUrl $Db $CollectionName

[string]$LogPath = "c:\temp\data"


$JS = New-Object System.Web.Script.Serialization.JavaScriptSerializer
$JS.MaxJsonLength = 32108864

$Files = Get-ChildItem -Path $LogPath | Select-Object FullName

foreach ($File in $Files){
    $StreamReader = New-Object System.IO.StreamReader($File.FullName)
    $Content = $StreamReader.ReadToEnd()
    $StreamReader.Dispose()

    $JSON = $JS.DeserializeObject($Content)
     
     foreach($Item in $JSON){
        if($Item.MessageLatencyType -ne 0){
            $Item.MessageLatency = $Item.MessageLatency.TotalMilliseconds
         }
     }

     $JSON | Add-MdbcData

}

7:

Седьмая версия PS обладает всеми недостатками предыдущей версии. Но имеет существенное отличие, а именно собственный конвертер для JSON. Забегая вперед скажу, что он довольно-таки неповоротливый.

Hidden text
Import-Module Mdbc
[string]$MongoUrl = "mongodb://localhost:27017"
[string]$Db = "sample_database_ps"
[string]$CollectionName = "sample_collection_7"

Connect-Mdbc $MongoUrl $Db $CollectionName

 [string]$LogPath = "c:\temp\data"

$Files = Get-ChildItem -Path $LogPath | Select-Object FullName

foreach ($File in $Files){
   
    $Reader = New-Object System.IO.StreamReader($File.FullName)
    $Content = $Reader.ReadToEnd()
    $Reader.Dispose()

    $JSON = $Content | ConvertFrom-Json # <--- Вот он
     
     foreach($Item in $JSON){
        if($Item.MessageLatencyType -ne 0){
            $Item.MessageLatency = $Item.MessageLatency.TotalMilliseconds
         }
     }
     
     $JSON | Add-MdbcData
}

GoLang

Признаться, я возлагал большие надежды на этот язык. Но о результатах мы порассуждаем чуть позже. А пока немного кода

Hidden text
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"path/filepath"
	"strconv"
	"time"

	"context"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

var collection *mongo.Collection
var ctx = context.TODO()

func main() {
	DBinit()
	files := []string{}

	files = GetFiles("c:\\temp\\data")
	for _, item := range files {
		WorkJSON(item)

	}

}

func GetFiles(Path string) []string {
	result := []string{}
	files, err := ioutil.ReadDir(Path)
	if err != nil {
		log.Fatal(err)
	}

	for _, file := range files {
		s := filepath.Join(Path, file.Name())
		result = append(result, s)
	}
	return result
}

func WorkJSON(Path string) {
	jsonFile, err := os.Open(Path)

	if err != nil {
		fmt.Println(err)
	}

	byteValue, _ := ioutil.ReadAll(jsonFile)

	jsonFile.Close()

	var result []map[string]interface{}
	var docs = make([]interface{}, 0)

	json.Unmarshal([]byte(byteValue), &result)

	for _, item := range result {
		if item["MessageLatencyType"] != 0.0 {
			item["MessageLatency"] = item["MessageLatency"].(map[string]interface{})["TotalMilliseconds"]
		}

		var strjsonDate = item["Timestamp"].(string)[6:19]

		strDate, err := strconv.ParseInt(strjsonDate, 10, 64)

		if err != nil {
			panic(err)
		}
		tm := time.Unix(strDate, 0)

		item["Timestamp"] = tm
		docs = append(docs, item)

	}

	collection.InsertMany(ctx, docs)

}

func DBinit() {

	clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
	client, err := mongo.Connect(ctx, clientOptions)
	if err != nil {
		log.Fatal(err)
	}

	err = client.Ping(ctx, nil)
	if err != nil {
		log.Fatal(err)
	}

	collection = client.Database("sample_database_node").Collection("sample_collection_GO")
}

Во время работы на Go меня почему то посетила ностальгия. Вспомнилось как около 25 лет назад меня приводили в замешательство слова про указатели ))

Ruby

Оказывается, в руби очень удобно работать с многопоточностью. И не смотря на то что в остальных версиях мы делали все в один поток, на руби я сделал форк который умеет работать в несколько потоков.

И так, не забываем про gem install mongo и вперед:

Hidden text
class FilesHarvester
    attr_reader :path, :Files
    def initialize (path) 
        @path = path
        self.GetFiles
    end

    def GetFiles
        @Files = Dir.glob(path) 
   end
end

class JSONWorker
    attr_reader :path, :json_data
    def initialize(path)
        require 'json'
        @path = path
        json_file = File.open path
        @json_data = JSON.load(json_file)

        @json_data.each do |jsitem|
            if (jsitem["MessageLatencyType"] != 0)
                ts = jsitem["MessageLatency"]["TotalMilliseconds"]
                jsitem["MessageLatency"] = ts
            end

            raw_date = jsitem["Timestamp"]
            right_data = self.convert_time(raw_date)
            jsitem["Timestamp"] = right_data
        end
    end #init

    def convert_time(raw_date)
        require 'time'
        clear_date = raw_date[6..-3].to_s
        reult = DateTime.strptime(clear_date, '%Q')
        return reult
    end #convert_time
end


class MongoDBClient
    attr_reader :collection
    def initialize
        require 'mongo'
        client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'sample_database_node')
        db = client.database
        @collection = client[:sample_collection_ruby]
    end
end


fileList = FilesHarvester.new(File.expand_path File.dirname(__FILE__) + '/data/*')
#fileList = FilesHarvester.new('F:/TEMP/Ruby/data_/*')
paths = fileList.Files

client = MongoDBClient.new

paths.each do |path|
    jw = JSONWorker.new(path)
    client.collection.insert_many(jw.json_data)
end

C#

В качестве сериализатора Json использовался Newtonsoft взятый NuGet-ом. Драйвера для Mongo официальные, взяты оттуда же.

Hidden text
using MongoDB.Bson;

using MongoDB.Bson.Serialization;

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;

using MongoDB.Driver;



namespace import_test_cs
{
    public class FilesWorker
    {
        public string[] files;
        public FilesWorker(string Path)
        {
            files = Directory.GetFiles(Path, "*.json",
                                         SearchOption.TopDirectoryOnly);

        }
    }
    public class DBWorker
    {
        private string connectionString = "mongodb://localhost:27017";
        private MongoClient client;
        private IMongoCollection<BsonDocument> DBCollection;

        public DBWorker()
        {
            Setup();
            BsonDocument chemp = new BsonDocument();
        }

        private void Setup()
        {
            client = new MongoClient(connectionString);
            IMongoDatabase database = client.GetDatabase("sample_database_node");
            DBCollection = database.GetCollection<BsonDocument>("sample_collection_cs");
        }


        public void SaveManyDoc(List<BsonDocument> bsonArray)
        {
            DBCollection.InsertMany(bsonArray);
        }

    }

    class Program
    {

        static DateTime ConvertDateTime(string source)
        {
            DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
            dtDateTime = dtDateTime.AddMilliseconds(double.Parse(source));
            return dtDateTime;
        }

        static void Main(string[] args)
        {

            string[] paths = new FilesWorker(@"c:/temp/data/").files;

            DBWorker DBClient = new DBWorker();


            foreach (string path in paths)
            {
                JArray JA1 = JArray.Parse(File.ReadAllText(path));
                List<BsonDocument> Docs = new List<BsonDocument>();


                foreach (var item in JA1)
                {
 
                    if (item.Value<int>("MessageLatencyType") != 0)
                    {
                        var i = item.Value<JObject>("MessageLatency");
                        item["MessageLatency"] = i.Value<int>("TotalMilliseconds");
                        
                    }

                    BsonDocument BDoc = BsonSerializer.Deserialize<BsonDocument>(item.ToString());
                    string DB = BDoc.GetValue("Timestamp").AsString;
                    DateTime RealTime = DateTime.Parse(DB);

                    BDoc.Remove("Timestamp");
                    BDoc.Add(new BsonElement("Timestamp", RealTime));

                    Docs.Add(BDoc);
                }

                DBClient.SaveManyDoc(Docs);
            }


        }
    }
}

JAVA

Код на java получился самый объемный. Для работы с json применялась легкая библиотека com.github.cliftonlabs:json-simple

FileHarvester.java
import java.util.ArrayList;
import java.util.List;
import java.io.File;

public class FileHarvester {
    private File _Path;
    public List<String> Files;
    public FileHarvester(String Path) {
        File _Path = new File(Path);
        this.Files = new ArrayList<String>();
        search(".*\\.json", _Path, Files);
    }

    public static void search(final String pattern, final File folder, List<String> result) {
        for (final File f : folder.listFiles()) {

            if (f.isDirectory()) {
                search(pattern, f, result);
            }

            if (f.isFile()) {
                if (f.getName().matches(pattern)) {
                    result.add(f.getAbsolutePath());
                }
            }

        }
    }
}

DBClient.java
import com.github.cliftonlabs.json_simple.JsonArray;
import com.github.cliftonlabs.json_simple.JsonObject;
import com.mongodb.client.MongoCollection;
import org.bson.Document;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

public class DBClient {

    private JsonArray JSON;
    private MongoCollection collection;

    public DBClient(JsonArray JSON, MongoCollection collection){
        this.JSON = JSON;
        this.collection = collection;

        PutDate();
    }

    private void PutDate(){

        List<Document> jsonList = new ArrayList<Document>();
        for (Object Item: this.JSON) {

            Document doc;//= new Document();
            String s = ((JsonObject)Item).toJson();
            doc = Document.parse(s);
            String d = doc.getString("Timestamp");
            LocalDateTime date = LocalDateTime.parse(d, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
            doc.put("Timestamp", date);
            jsonList.add(doc);
        }
        this.collection.insertMany(jsonList);

    }

}

JSONWorker.java
import com.github.cliftonlabs.json_simple.*;

import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

import java.util.TimeZone;

public class JSONWorker {

    public JsonArray JSON;
    public String JSONString;
    JSONWorker(String FilePath) throws IOException, JsonException {
        Reader reader = Files.newBufferedReader(Paths.get(FilePath));

        JsonArray TempJSON = (JsonArray) Jsoner.deserialize(reader);

        for (Object Item: TempJSON) {
            JsonKey Key = Jsoner.mintJsonKey("MessageLatencyType", 0);

            int i = ((JsonObject)Item).getInteger(Key);
            if(i != 0)
            {
                JsonKey KeyFromMountain = Jsoner.mintJsonKey("MessageLatency", null);
                JsonObject MS =  ((JsonObject)Item).getMapOrDefault(KeyFromMountain);

                JsonKey KeyFromTotalMilliseconds = Jsoner.mintJsonKey("TotalMilliseconds", 0);
                int TotalMilliseconds = MS.getInteger(KeyFromTotalMilliseconds);

                ((JsonObject)Item).put("MessageLatency", TotalMilliseconds);

            }

            JsonKey DataKey = Jsoner.mintJsonKey("Timestamp", 0);

            String DataString = ((JsonObject)Item).getString(DataKey);
            String ShortDataString = DataString.substring(6, 19);
            long LongDataString = Long.parseLong(ShortDataString);
            Timestamp ts = new Timestamp(LongDataString);

            DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            df.setTimeZone(TimeZone.getTimeZone("UTC"));
            String my8601formattedDate = df.format(ts);

            ((JsonObject)Item).put("Timestamp", my8601formattedDate);

        } //for

        this.JSON = TempJSON;
        this.JSONString = TempJSON.toJson();

    }

}

ImportTest.java
import com.github.cliftonlabs.json_simple.JsonException;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ImportTest {

    public static void main(String[] args) throws JsonException, IOException {
       
        Path path = Paths.get("");
        String s = path.toAbsolutePath().toString();
        String DataPath = s + File.separator + "data";
        System.out.println("Data path:" + DataPath);

        FileHarvester FilesHarvest = new FileHarvester(DataPath);
        MongoClient mongoClient = new MongoClient();
        MongoDatabase database = mongoClient.getDatabase("sample_database_node");
        MongoCollection collection = database.getCollection("sample_collection_java");

        for (String FilePath : FilesHarvest.Files) {
            JSONWorker JSONw = new JSONWorker(FilePath);
            DBClient Mongo = new DBClient(JSONw.JSON, collection);

        } //for

        mongoClient.close();


    }

}

Результаты

И так. Для чего же мы все это делали?

На самом деле, ни для чего ))). Просто это достаточно легкая задача для того что бы попробовать что-то новое, какой-то новый язык. Понять его удобство, или скорость разработки, или красоту цветовой палитры, ну или любой другой субъективный параметр.

Но коль уж мы потратили столько времени, давайте все-таки сделаем какие-то количественные выводы.

Хм… Что же вызывает такую большую разницу в скорости?

Для ответа на этот вопросы мы условно разделили программу на части:

  • работа с файлами

  • работа с JSON

  • работа с БД

На каждую часть повесили по таймеру и стали наблюдать. Оказалось, что практически в любом языке самым медленным является работа с объектами JSON.

Замена целого объекта MessageLatency на поле типа int самая долгая операция в любом языке и библиотеке по работе с JSON, кроме JS. Не представляю, как JS работает с объектами JSON, но выглядит так как будто он вообще не заморачивается десериализацией, и работает с ними как с массивами строк.

P.S.:

А вот внеконкурсные результаты работы в несколько потоков на языке Ruby.

Многопоточный Ruby обошел Go по скорости. Хотя это конечно читерство, но все равно приятно ))

Просто мысли вслух

Как я и говорил в начале, статья написано исключительно в развлекательных целях. Думаю, она вполне подойдет для легко чтива под чашку чая.

Исходя из вышесказанного не стоит искать здесь какой-то истины, ровно как и не стоит принимать полученные данные в качестве призыва к выбору инструмента разработки.

Что касается меня, то я сделал то что давно хотел - в первый раз попробовал Ruby и Go, а также посмотрел по пристальней на Java. Ruby оказался очень интересным языком, вероятно я продолжу общение с ним. Мой основной инструмент работы (PS7) оказался в топе, жалко что с конца :-)

Надеюсь кому-то это было интересно :-)

Всем спасибо за внимание.

Комментарии (23)


  1. vassabi
    29.10.2022 22:53
    +2

    Надеюсь кому-то это было интересно :-)


    у вас отличная и нескучная статья!

    (продолжает дальше смотреть как в соседнем ноутбуке один С & С++ & Qt проект для automotive компилирутеся и линкуется в течение 6 часов)


    1. NetFoXZX Автор
      30.10.2022 21:07

      Спасибо )) И желаю терпения Вам ))


  1. anayks
    30.10.2022 00:14
    +6

    Хочу поделиться с вами историей как два админа программили и что из этого вышло.

    Ничего не получилось...

    Не понял, в чём смысл говорить о многопоточности Руби и не использовать горутины в Go вообще.


    1. NetFoXZX Автор
      30.10.2022 21:17

      Про руби это просто отступление. Результаты не участвовали в общем забеге. Мне просто было интересно как это работает именно в руби.

      К тому же у нас же не стояла задача использовать многопоточность или как то ещё оптимизировть работу. Мы использовали максимально простой и прямолинейный подход. Можете считать что это Just for fun ))


  1. novoselov
    30.10.2022 09:47

    На Java обход файлов можно сделать в 1 строку, примерно так

    Files.walk(Paths.get(directory))
         .filter(Files::isRegularFile)
         // .filter(pattern::matches)
         .collect(Collectors.toList())

    Причем стоит regex pattern вынести в final static singleton, аналогично про DateTimeFormatter (не используйте SimpleDateFormat вообще).

    Плюс используется одна из худших json библиотек по производительности, посмотрите лучше в сторону https://github.com/ngs-doo/dsl-json или http://jsoniter.com/


    1. NetFoXZX Автор
      30.10.2022 21:19

      Спасибо, в следующий раз я буду иметь это ввиду.

      Но прошу учесть что на java я писал вообще первый раз и в таких тонкостях ещё не разбираюсь.


    1. klimkinMD
      31.10.2022 10:43

      А 1 строка работает быстрее?


      1. novoselov
        01.11.2022 07:35

        Не сравнивал, но скорее да.

        Первый вариант использует старый IO API в котором даже баги не чинят https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4285834

        Второй вариант должен использовать новый NIO API и не создавать лишнего мусора для хранения списка файлов.


  1. sophist
    30.10.2022 11:36
    +4

    …оказался в топе, жалко…

    С первого раза неправильно прочитал.


  1. mozg3000tm
    30.10.2022 13:37

    Разве логи не хранятся в виде текста в файлах .log?

    Причем тут .json?

    Или я что то упускаю? Для чего вся эта работа с json?


    1. NetFoXZX Автор
      30.10.2022 21:06

      Честно говоря я не в курсе. На сколько я понимаю коллега либо сам написал экспортер в json, либо использовал какие то стандартные средства для этого. Может быть раньше у него был другой механизм обработки. Но суть в том что входные данные у меня были именно такими.


  1. box0547
    30.10.2022 19:02

    C#

    Newtonsoft

    Вам бы стоило использовать System.Text.Json, последнюю LTS версию .Net6, а ещё лучше .Net7 ????


    1. NetFoXZX Автор
      30.10.2022 21:09

      Наверное да, но чисто субъективно мней как то роднее 4.8. Но на коре наверное тоже есть смысл попробовать написать.


      1. box0547
        30.10.2022 23:34
        +1

        Понял, на 4.8 тоже доступен.

        на коре наверное тоже есть смысл попробовать написать

        Конечно есть!


  1. awk795
    01.11.2022 08:09

    PowerShell 5.1 - имеет из коробки ConvertFrom-Json. Да менее функциональный, но....

    $Files = Get-ChildItem -Path $LogPath | Select-Object FullName

    А для чего сия манипуляция?

    Почему не Get-ChildItem -Path $LogPath | ForEach-Object ?


    1. NetFoXZX Автор
      01.11.2022 08:54

      Да, действительно имеет. Похоже я немного перепутал причино-следственные связи.

      В ps7 мне не удалось загрузить System.Web.Script.Serialization.JavaScriptSerializer, по этому использовал стандартный командлет. Но во время написания стать мне почему то вспомнилось наоборот - что в ps5 не смог найти  ConvertFrom-Json.

      Почему не Get-ChildItem -Path $LogPath | ForEach-Object ?

      1. Не люблю такие конструкции. Их не удобно дебажить.

      2. Инверсия зависимости. Такой цикл проще обернуть например в функцию или метод, ведь в качестве параметра он принимает только абстрактный список.

      3. ИМХО такой код более читабельный.

      Я понимаю что в рамках 40 строк скрипта это все притянуто за уши. Но в скриптах по 900 и более строк ООП+SOLID заметно упрощают жизнь. Описал класс, реализовал методы, в экземпляре получил что то абстрактное, передал в другой класс. Кода классов на 890 строк, алгоритма работы скрипта на 10. ))


      1. awk795
        01.11.2022 09:33
        +1

        1. SOLID - это хорошая привычка, даже без ООП.

        2. 900 строк в скрипте? А точно тогда надо брать скриптовый язык? У нас нет другого выбора?

        3. Я имел ввиду: "Зачем получать массив объектов типа Файл, а дальше преобразовывать его в массив объектов с одним полем в виде строки, если сразу можно передать сие чудо в конвейер?".


        1. NetFoXZX Автор
          01.11.2022 11:33

          1. Ааа... Оу.. Да, конечно, совершенно не за чем ))) Кажется я хотел получить просто массив строк, но зачем то сделал массив объектов с одним полем. ))

          1. Конечно есть, а зачем? Скрипт нативный для всех систем, соответственно его можно запускать из шары (не нужен деплой на все сервера). Скрипт не нуждается в дополнительных библиотеках для совей работы, т.к. использует Import-PSSession или -Command или вообще Invoke-Command. Скрипт написан так, что ему все равно на версию ps. Скрипт хорошо структурирован и комментирован. В случае необходимости исправления могут вноситься фактически на лету из любого места с помощью блокнота.

          А теперь смотри, мы берем какой нить c# и пишем там один единственный метод

          using System.Net.NetworkInformation;
          
          public static bool PingHost(string nameOrAddress)
                  {
                      bool pingable = false;
                      Ping pinger = null;
          
                      try
                      {
                          pinger = new Ping();
                          PingReply reply = pinger.Send(nameOrAddress);
                          pingable = reply.Status == IPStatus.Success;
                      }
                      catch (PingException)
                      {
                          // Discard PingExceptions and return false;
                      }
                      finally
                      {
                          if (pinger != null)
                          {
                              pinger.Dispose();
                          }
                      }
          
                      return pingable;
                  }

          Компилируем, берем екзешник кладем в шару.

          Запускаем и "The application to execute does not exist: '\\share\share\ConsoleApp14.dll'"

          Ага, ок, забыли библиотеку, сами виноваты.

          Перенесли, запускаем второй раз и "A fatal error occurred. The required library hostfxr.dll could not be found." А это чо такое, я же ни чего такого не использовал? А это не тот фреймфорк. И это даже без использования каких то иных библиотек.

          Прекрасно когда есть возможность держать все сервера в одинаковом и актуальном состоянии, но в реальной жизни к сожалению это не всегда возможно ))

          Резюмирую все вышесказанное. Скриптом проще ))) А т.к. инструмент выбирается соответственно задачи, то и нет смысла от него отказываться. ))


          1. awk795
            01.11.2022 12:01

            1. Хотел ведь написать, что брать скрипт из 900 строк так же малооправдано как для копирования файлов разворачивать SAP.


            1. NetFoXZX Автор
              01.11.2022 12:36

              Для наглядности. Есть скрипт который висит на обработчике события создания пользователя в AD

              Вот что он делает:

              • Вычитка самого события из журнала винды.

              • Установка необходимых для работы, сторонних систем, атрибутов пользователю АД.

              • Вычитка данных о почтовых серверах из AD.

              • Определение первого доступного почтового сервера.

              • Создание почтового ящика на удаленном сервере.

              • Определение наименее загруженного файлового сервера, на основании объемов и количества уже существующих монтировок.

              • Создание VHD диска на определенном сервере.

              • Монтирование этого диска на удаленном сервере, выдача прав на этот диск.

              • Назначение FSRM квоты на этот диск.

              • Внесение изменений в JSON файл на этом сервере для другого скрипта автоматической монтировки.

              • Присвоение этого диска в качестве хома пользователю AD.

              • Создание служебной папки папки на удаленном сервере, назначение прав.

              • Создание пользователя в корпоративном месенджере.

              • Формирование и отправка двух приветственных письма, с HTML раскраской, картинками, вложенными фалами.

              • Формирование и отправка лог работы скрипта админам.

                Все это оформлено классами, украшено обработчиками разнообразных ошибок, логированием и т.д.



              1. awk795
                01.11.2022 12:49

                Вас долго пытали, что бы вы отказались от C#/Python/Etc? :)


                1. NetFoXZX Автор
                  01.11.2022 13:25

                  Кажется мы скоро свалимся в холивар PS vs etc. ))))

                  На самом деле, нативность и доступность с лихвой перекрывает возможные неудобства.

                  А дабы вас порадовать скажу что я реализовывал веб сервер с REST на PS. Писал GUI формы в винде на нем же. И даже писал некий аналог DHCP сервера, на основе группы, для дальнейшей интеграции в cisco. Получился не плохой аналог куска cisco ise ????


                  1. awk795
                    01.11.2022 13:39
                    +1

                    У меня то же камин аут - я программист сценарист 1С... :)