/// <summary>
        ///     Initializes a new instance of the <see cref="AmazonS3Sink" /> class.
        /// </summary>
        /// <param name="formatter">The formatter.</param>
        /// <param name="path">The path.</param>
        /// <param name="fileSizeLimitBytes">The file size limit bytes.</param>
        /// <param name="buffered">if set to <c>true</c> [buffered].</param>
        /// <param name="encoding">The encoding.</param>
        /// <param name="rollingInterval">The rolling interval.</param>
        /// <param name="retainedFileCountLimit">The retained file count limit.</param>
        /// <param name="hooks">The hooks.</param>
        /// <param name="bucketName">The Amazon S3 bucket name.</param>
        /// <param name="endpoint">The Amazon S3 endpoint.</param>
        /// <param name="awsAccessKeyId">The Amazon S3 access key id.</param>
        /// <param name="awsSecretAccessKey">The Amazon S3 secret access key.</param>
        /// <returns>A <see cref="LoggerConfiguration" /> to use with Serilog.</returns>
        /// <exception cref="ArgumentNullException">
        ///     addSink
        ///     or
        ///     formatter
        ///     or
        ///     path
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     Negative value provided; file size limit must be non-negative. - fileSizeLimitBytes
        ///     or
        ///     At least one file must be retained. - retainedFileCountLimit
        ///     or
        ///     Buffered writes are not available when file sharing is enabled. - buffered
        ///     or
        ///     File lifecycle hooks are not currently supported for shared log files. - hooks
        /// </exception>
        public AmazonS3Sink(
            ITextFormatter formatter,
            string path,
            long?fileSizeLimitBytes,
            bool buffered,
            Encoding encoding,
            RollingInterval rollingInterval,
            int?retainedFileCountLimit,
            FileLifecycleHooks hooks,
            string bucketName,
            RegionEndpoint endpoint,
            string awsAccessKeyId,
            string awsSecretAccessKey)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                throw new ArgumentNullException(nameof(path));
            }

            if (string.IsNullOrWhiteSpace(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }

            if (string.IsNullOrWhiteSpace(awsAccessKeyId))
            {
                throw new ArgumentNullException(nameof(awsAccessKeyId));
            }

            if (string.IsNullOrWhiteSpace(awsSecretAccessKey))
            {
                throw new ArgumentNullException(nameof(awsSecretAccessKey));
            }

            if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0)
            {
                throw new ArgumentException("Negative value provided; file size limit must be non-negative.");
            }

            if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1)
            {
                throw new ArgumentException(
                          "Zero or negative value provided; retained file count limit must be at least 1.");
            }

            this.sink = new RollingFileSink(
                path,
                formatter,
                fileSizeLimitBytes,
                retainedFileCountLimit,
                encoding,
                buffered,
                rollingInterval,
                true,
                hooks,
                bucketName,
                endpoint,
                awsAccessKeyId,
                awsSecretAccessKey);
        }
        /// <summary>
        ///     Initializes a new instance of the <see cref="RollingFileSink" /> class.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="textFormatter">The text formatter.</param>
        /// <param name="fileSizeLimitBytes">The file size limit bytes.</param>
        /// <param name="retainedFileCountLimit">The retained file count limit.</param>
        /// <param name="encoding">The encoding.</param>
        /// <param name="buffered">if set to <c>true</c> [buffered].</param>
        /// <param name="rollingInterval">The rolling interval.</param>
        /// <param name="rollOnFileSizeLimit">if set to <c>true</c> [roll on file size limit].</param>
        /// <param name="fileLifecycleHooks">The file lifecycle hooks.</param>
        /// <param name="bucketName">The Amazon S3 bucket name.</param>
        /// <param name="endpoint">The Amazon S3 endpoint.</param>
        /// <param name="awsAccessKeyId">The Amazon S3 access key id.</param>
        /// <param name="awsSecretAccessKey">The Amazon S3 access key.</param>
        /// <exception cref="ArgumentNullException">An <see cref="ArgumentNullException" /> thrown when the path is null.</exception>
        /// <exception cref="ArgumentException">
        ///     Negative value provided; file size limit must be non-negative.
        ///     or
        ///     Zero or negative value provided; retained file count limit must be at least 1.
        /// </exception>
        public RollingFileSink(
            string path,
            ITextFormatter textFormatter,
            long?fileSizeLimitBytes,
            int?retainedFileCountLimit,
            Encoding encoding,
            bool buffered,
            RollingInterval rollingInterval,
            bool rollOnFileSizeLimit,
            FileLifecycleHooks fileLifecycleHooks,
            string bucketName,
            RegionEndpoint endpoint,
            string awsAccessKeyId,
            string awsSecretAccessKey)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                throw new ArgumentNullException(nameof(path));
            }

            if (string.IsNullOrWhiteSpace(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }

            if (string.IsNullOrWhiteSpace(awsAccessKeyId))
            {
                throw new ArgumentNullException(nameof(awsAccessKeyId));
            }

            if (string.IsNullOrWhiteSpace(awsSecretAccessKey))
            {
                throw new ArgumentNullException(nameof(awsSecretAccessKey));
            }

            if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0)
            {
                throw new ArgumentException("Negative value provided; file size limit must be non-negative.");
            }

            if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1)
            {
                throw new ArgumentException(
                          "Zero or negative value provided; retained file count limit must be at least 1.");
            }

            this.bucketName             = bucketName;
            this.awsAccessKeyId         = awsAccessKeyId;
            this.awsSecretAccessKey     = awsSecretAccessKey;
            this.endpoint               = endpoint;
            this.pathRoller             = new PathRoller(path, rollingInterval);
            this.textFormatter          = textFormatter;
            this.fileSizeLimitBytes     = fileSizeLimitBytes;
            this.retainedFileCountLimit = retainedFileCountLimit;
            this.encoding               = encoding;
            this.buffered               = buffered;
            this.rollOnFileSizeLimit    = rollOnFileSizeLimit;
            this.fileLifecycleHooks     = fileLifecycleHooks;
        }
        /// <summary>   Initializes a new instance of the <see cref="RollingFileSink" /> class. </summary>
        /// <exception cref="ArgumentNullException">
        ///     An <see cref="ArgumentNullException" /> thrown
        ///     when the path is null.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     Negative value provided; file size limit must be
        ///     non-negative. or Zero or negative value provided;
        ///     retained file count limit must be at least 1.
        /// </exception>
        /// <param name="path">                     The path. </param>
        /// <param name="textFormatter">            The text formatter. </param>
        /// <param name="fileSizeLimitBytes">       The file size limit bytes. </param>
        /// <param name="retainedFileCountLimit">   The retained file count limit. </param>
        /// <param name="encoding">                 The encoding. </param>
        /// <param name="buffered">                 if set to <c>true</c> [buffered]. </param>
        /// <param name="rollingInterval">          The rolling interval. </param>
        /// <param name="rollOnFileSizeLimit">      if set to <c>true</c> [roll on file size limit]. </param>
        /// <param name="fileLifecycleHooks">       The file lifecycle hooks. </param>
        /// <param name="bucketName">               The Amazon S3 bucket name. </param>
        /// <param name="endpoint">                 The Amazon S3 endpoint. </param>
        /// <param name="autoUploadEvents">         Automatically upload all events immediately. </param>
        /// <param name="failureCallback">          (Optional) The failure callback. </param>
        /// <param name="bucketPath">               (Optional) The Amazon S3 bucket path. </param>
        public RollingFileSink(
            string path,
            ITextFormatter textFormatter,
            long?fileSizeLimitBytes,
            int?retainedFileCountLimit,
            Encoding encoding,
            bool buffered,
            RollingInterval rollingInterval,
            bool rollOnFileSizeLimit,
            FileLifecycleHooks fileLifecycleHooks,
            string bucketName,
            RegionEndpoint endpoint,
            bool autoUploadEvents,
            Action <Exception> failureCallback = null,
            string bucketPath = null)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                throw new ArgumentNullException(nameof(path));
            }

            if (string.IsNullOrWhiteSpace(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }

            if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0)
            {
                throw new ArgumentException("Negative value provided; file size limit must be non-negative.");
            }

            if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1)
            {
                throw new ArgumentException(
                          "Zero or negative value provided; retained file count limit must be at least 1.");
            }

            if (failureCallback != null)
            {
                this.FailureCallback = failureCallback;
            }

            this.bucketName             = bucketName;
            this.endpoint               = endpoint;
            this.pathRoller             = new PathRoller(path, rollingInterval);
            this.textFormatter          = textFormatter;
            this.fileSizeLimitBytes     = fileSizeLimitBytes;
            this.retainedFileCountLimit = retainedFileCountLimit;
            this.encoding               = encoding;
            this.buffered               = buffered;
            this.rollOnFileSizeLimit    = rollOnFileSizeLimit;
            this.fileLifecycleHooks     = fileLifecycleHooks;
            this.autoUploadEvents       = autoUploadEvents;
            this.bucketPath             = bucketPath;
        }
        /// <summary>   Initializes a new instance of the <see cref="FileSink" /> class. </summary>
        /// <exception cref="ArgumentNullException">        path or textFormatter. </exception>
        /// <exception cref="ArgumentException">
        ///     Negative value provided; file size limit must
        ///     be non-negative.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        ///     The file lifecycle hook
        ///     FileLifecycleHooks.OnFileOpened.
        /// </exception>
        /// <param name="path">                 The path. </param>
        /// <param name="textFormatter">        The text formatter. </param>
        /// <param name="fileSizeLimitBytes">   The file size limit bytes. </param>
        /// <param name="encoding">             The encoding. </param>
        /// <param name="buffered">             if set to <c>true</c> [buffered]. </param>
        /// <param name="hooks">                The hooks. </param>
        public FileSink(
            string path,
            ITextFormatter textFormatter,
            long?fileSizeLimitBytes,
            Encoding encoding,
            bool buffered,
            FileLifecycleHooks hooks)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                throw new ArgumentNullException(nameof(path));
            }

            if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0)
            {
                throw new ArgumentException("Negative value provided; file size limit must be non-negative.");
            }

            this.textFormatter      = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter));
            this.fileSizeLimitBytes = fileSizeLimitBytes;
            this.buffered           = buffered;

            var directory = Path.GetDirectoryName(path);

            if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }

            Stream outputStream = this.underlyingStream = File.Open(
                path,
                FileMode.Append,
                FileAccess.Write,
                FileShare.Read);

            if (this.fileSizeLimitBytes != null)
            {
                outputStream = this.countingStreamWrapper = new WriteCountingStream(this.underlyingStream);
            }

            // Parameter reassignment.
            encoding = encoding ?? new UTF8Encoding(false);

            if (hooks != null)
            {
                outputStream = hooks.OnFileOpened(outputStream, encoding) ?? throw new InvalidOperationException(
                                         $"The file lifecycle hook `{nameof(FileLifecycleHooks.OnFileOpened)}(...)` returned `null`.");
            }

            this.output = new StreamWriter(outputStream, encoding);
        }